How to Use absl::Cleanup for RAII-Style Scope Exit Callbacks in Abseil C++
absl::Cleanup implements the scope guard idiom by storing a callable that automatically executes when the object goes out of scope, providing deterministic cleanup without heap allocation.
The absl::Cleanup class in the abseil/abseil-cpp repository provides a lightweight, zero-overhead mechanism for ensuring resources are released when execution leaves a scope. Unlike std::unique_ptr with custom deleters, absl::Cleanup accepts any callable—lambdas, functors, or function pointers—making it ideal for complex teardown logic that doesn't fit a simple pointer model. This utility is header-only and lives in the absl/cleanup/ module.
Creating a Cleanup Object
The public API resides in [absl/cleanup/cleanup.h](https://github.com/abseil/abseil-cpp/blob/master/absl/cleanup/cleanup.h). You can instantiate absl::Cleanup using either C++17 class template argument deduction (CTAD) or the absl::MakeCleanup factory function for C++11 compatibility.
C++17 Class Template Argument Deduction
With C++17, the compiler automatically deduces the callback type, enabling concise syntax:
#include "absl/cleanup/cleanup.h"
void ProcessFile(const char* path) {
FILE* file = fopen(path, "r");
if (!file) return;
absl::Cleanup file_closer = [file] { fclose(file); };
// Process data...
// fclose called automatically when file_closer destructs
}
C++11 absl::MakeCleanup Factory
For pre-C++17 compilers, use the absl::MakeCleanup helper function to avoid explicit template arguments:
auto file_closer = absl::MakeCleanup([file] { fclose(file); });
Both approaches create a move-only object that holds your callable in internal storage.
How absl::Cleanup Works Under the Hood
The implementation leverages placement-new and manual lifecycle management to avoid heap allocation while supporting callables of arbitrary size.
Internal Storage Architecture
The internal implementation in [absl/cleanup/internal/cleanup.h](https://github.com/abseil/abseil-cpp/blob/master/absl/cleanup/internal/cleanup.h) uses cleanup_internal::Storage<Callback> to manage the callback lifetime. This storage class:
- Allocates space using a raw buffer (
callback_buffer_) sized for the callable type - Constructs the callback via placement-new to avoid dynamic allocation
- Tracks engagement state via
is_callback_engaged_
When you create a Cleanup object, the callable is placement-new'd into the stack-allocated buffer within the Storage object.
Engagement Tracking and Execution Guarantees
The destructor of Cleanup checks IsCallbackEngaged(). If engaged, it invokes InvokeCallback() followed by DestroyCallback(). This ensures:
- Exactly-once execution: The callback runs either on scope exit, explicit
Invoke(), or not at all ifCancel()is called - No heap allocation: The storage lives within the
Cleanupobject itself - Signal-safety: No locks or dynamic allocation occurs during destruction (assuming your callback is also signal-safe)
Controlling Cleanup Execution
While automatic execution on scope exit is the default behavior, absl::Cleanup provides explicit control for unusual control flows.
Early Invocation with Invoke()
Call Invoke() to execute the callback immediately and disengage the guard:
absl::Cleanup guard = [] { Log("Cleanup executed"); };
// Do work...
std::move(guard).Invoke(); // Calls Log now; destructor becomes no-op
Note that Invoke() requires moving from the cleanup object because Cleanup is move-only (non-copyable).
Canceling Execution with Cancel()
Prevent the callback from running by calling Cancel():
absl::Cleanup guard = [] { ReleaseResource(); };
if (resource_already_released) {
std::move(guard).Cancel(); // Disengages the callback
}
// No action taken when guard destructs
Both Invoke() and Cancel() move-from the object to enforce single ownership semantics.
Practical Examples
Resource Management with Early Returns
The canonical use case handles multiple resources with complex exit paths:
absl::Status CopyGoodData(const char* source_path, const char* sink_path) {
FILE* source_file = fopen(source_path, "r");
if (!source_file) return absl::NotFoundError("No source file");
absl::Cleanup source_closer = [source_file] { fclose(source_file); };
FILE* sink_file = fopen(sink_path, "w");
if (!sink_file) return absl::NotFoundError("No sink file");
auto sink_closer = absl::MakeCleanup([sink_file] { fclose(sink_file); });
while (ReadData(source_file, &data)) {
if (!data.IsGood()) {
return absl::FailedPreconditionError("Read bad data"); // Both cleanups run
}
SaveData(sink_file, &data);
}
return absl::OkStatus(); // Both cleanups run
}
Non-Copyable Callables
absl::Cleanup supports move-only functors, making it compatible with unique resources:
struct NonCopyable {
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable(NonCopyable&&) = default;
void operator()() const { /* cleanup code */ }
};
absl::Cleanup cleanup = NonCopyable{}; // Works fine
Summary
absl::Cleanupprovides zero-overhead RAII scope guards for arbitrary teardown logic.- Implementation uses placement-new in [
absl/cleanup/internal/cleanup.h](https://github.com/abseil/abseil-cpp/blob/master/absl/cleanup/internal/cleanup.h) to store callables without heap allocation. - Creation uses C++17 CTAD or
absl::MakeCleanupfor C++11 compatibility via the public header [absl/cleanup/cleanup.h](https://github.com/abseil/abseil-cpp/blob/master/absl/cleanup/cleanup.h). - Control explicitly via
std::move(obj).Invoke()for early execution orstd::move(obj).Cancel()to suppress execution. - Semantics are move-only, ensuring unique ownership of the cleanup action.
Frequently Asked Questions
Is absl::Cleanup thread-safe?
absl::Cleanup is not thread-safe. The class provides no internal synchronization. If you need to share cleanup logic across threads, you must provide your own locking or ensure the Cleanup object is accessed by only one thread. However, the class is signal-safe because it contains no locks and only performs placement-new and destruction operations.
What is the difference between absl::Cleanup and std::unique_ptr with a custom deleter?
std::unique_ptr manages a single pointer resource, while absl::Cleanup manages arbitrary callable logic. Use absl::Cleanup when you need to execute complex teardown code—such as logging, state restoration, or multiple resource releases—that doesn't fit the pointer ownership model. Additionally, absl::Cleanup provides explicit Cancel() and Invoke() methods for manual control.
Can I use absl::Cleanup in C++11 code?
Yes. While C++17 allows direct initialization via class template argument deduction, C++11 users can create cleanup objects using the absl::MakeCleanup factory function. This function deduces the type automatically and returns a properly instantiated Cleanup object compatible with older standards.
Does absl::Cleanup allocate memory on the heap?
No. The implementation stores the callable in a stack-allocated buffer using placement-new. The cleanup_internal::Storage class in absl/cleanup/internal/cleanup.h manages a raw byte buffer sized for the specific callable type, ensuring zero heap allocation overhead and predictable performance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →