# How to Use absl::Cleanup for RAII-Style Scope-Exit Callbacks in Abseil C++

> Learn how to use absl Cleanup for RAII-style scope-exit callbacks in Abseil C++. Ensure deterministic resource cleanup without heap allocation.

- Repository: [Abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)
- Tags: how-to-guide
- Published: 2026-07-12

---

**`absl::Cleanup` stores a callable that executes automatically when the object goes out of scope, providing deterministic resource cleanup without heap allocation.**

The `absl::Cleanup` class in the Abseil C++ library implements the classic **scope guard** (RAII) idiom for managing resource lifetimes. Defined in [`absl/cleanup/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/cleanup.h), this utility captures lambdas or functors that run during destruction, ensuring cleanup code executes exactly once—whether during normal scope exit, early returns, or exception unwinding. Unlike manual cleanup patterns, `absl::Cleanup` eliminates boilerplate while guaranteeing zero dynamic memory allocation through placement-new storage.

## Basic Usage: C++17 Deduction vs. C++11 Factory

You can create a cleanup object using **class template argument deduction (CTAD)** in C++17 or the `absl::MakeCleanup` factory function for C++11 compatibility.

```cpp
#include "absl/cleanup/cleanup.h"

absl::Status ProcessFile(const char* path) {
  FILE* file = fopen(path, "r");
  if (!file) return absl::NotFoundError("File not found");

  // C++17: Automatic deduction of callback type
  absl::Cleanup file_closer = [file] { fclose(file); };

  // C++11: Explicit factory function
  auto buffer_guard = absl::MakeCleanup([&] { ClearBuffer(); });

  // Process data...
  if (error_condition) {
    return absl::InternalError("Processing failed");  // file_closer runs automatically
  }

  return absl::OkStatus();  // Both cleanups execute here
}

```

The `Cleanup` constructor in [`absl/cleanup/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/cleanup.h) uses a `cleanup_internal::Tag` template parameter to forbid explicit template arguments, enforcing usage solely through deduction or `MakeCleanup`.

## Internal Architecture and Zero-Allocation Storage

The implementation relies on `cleanup_internal::Storage<Callback>` defined in [`absl/cleanup/internal/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/internal/cleanup.h). This storage class performs **placement-new** construction of the callable inside a raw byte buffer (`callback_buffer_`), avoiding heap allocation while enabling eager destruction.

Key implementation details include:

- **Engagement tracking**: A boolean flag `is_callback_engaged_` tracks whether the callback is still active and should be invoked during destruction.
- **Manual lifecycle management**: The destructor checks `IsCallbackEngaged()`; if true, it calls `InvokeCallback()` followed by `DestroyCallback()` to ensure the cleanup runs exactly once.
- **Signal safety**: The class contains no locks and only performs placement-new and destructor calls, making it safe for use in signal handlers (provided the user’s callback is also signal-safe).

## Explicit Control: Early Invocation and Cancellation

`absl::Cleanup` provides **move-only semantics** with methods to control execution timing. Both `Invoke()` and `Cancel()` require `std::move` because they consume the cleanup object's state.

### Early Invocation with `Invoke()`

Call `std::move(cleanup).Invoke()` to execute the callback immediately and disarm the destructor:

```cpp
absl::Cleanup transaction_guard = [] { RollbackTransaction(); };

// Commit successful—rollback not needed
CommitTransaction();
std::move(transaction_guard).Invoke();  // Calls Rollback now (or use Cancel for no-op)

```

### Cancellation with `Cancel()`

Use `std::move(cleanup).Cancel()` to suppress callback execution entirely:

```cpp
absl::Cleanup guard = [] { ReleaseResource(); };

if (resource_already_released) {
  std::move(guard).Cancel();  // Prevent ReleaseResource from running
}
// Guard destroyed safely with no side effects

```

## Working with Move-Only and Non-Copyable Callables

`absl::Cleanup` is **move-constructible** (`Cleanup(Cleanup&&) = default`) but not copyable, ensuring unique ownership of the cleanup action. This design supports non-copyable callable objects:

```cpp
struct NonCopyableDeleter {
  NonCopyableDeleter() = default;
  NonCopyableDeleter(const NonCopyableDeleter&) = delete;
  NonCopyableDeleter(NonCopyableDeleter&&) = default;
  void operator()() const { /* cleanup code */ }
};

absl::Cleanup cleanup = NonCopyableDeleter{};  // Compiles: move-only semantics preserved

```

## Summary

- **`absl::Cleanup`** provides deterministic scope-exit callbacks through RAII, implemented in [`absl/cleanup/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/cleanup.h).
- **Zero allocation**: Callbacks stored via placement-new in `callback_buffer_` within [`absl/cleanup/internal/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/internal/cleanup.h).
- **Explicit control**: `std::move(c).Invoke()` runs callbacks early; `std::move(c).Cancel()` prevents execution.
- **Move-only**: Cannot be copied, ensuring unique ownership and supporting non-copyable callables.
- **Signal-safe**: Lock-free implementation suitable for signal handlers when callbacks are signal-safe.

## Frequently Asked Questions

### What is the difference between absl::Cleanup and std::unique_ptr with a custom deleter?

**`absl::Cleanup`** is designed for arbitrary scope-exit actions, not just resource destruction. While `std::unique_ptr` manages object ownership through a deleter, `absl::Cleanup` accepts any callable (including captures) and provides explicit `Cancel()` and `Invoke()` methods. Additionally, `absl::Cleanup` guarantees stack-only storage through placement-new, whereas `std::unique_ptr` typically manages heap-allocated objects.

### Can I use absl::Cleanup in signal handlers?

**Yes**, provided your callback is signal-safe. The `absl::Cleanup` implementation contains no locks, atomic operations, or dynamic allocation—only placement-new and destructor calls. According to the Abseil source code, this makes the class itself safe for signal handlers, though you must ensure the callable you store (e.g., `fclose` or custom logging) is also async-signal-safe.

### Why do Invoke() and Cancel() require std::move?

**These methods consume the object's engagement state.** `absl::Cleanup` implements move-only semantics to ensure unique ownership of the cleanup action. Calling `std::move(cleanup).Invoke()` transfers ownership to the method, explicitly disarming the destructor to prevent double-execution. This design enforces at compile-time that the cleanup object cannot be used after manual invocation or cancellation.

### How do I store absl::Cleanup in a container or class member?

**You cannot copy `absl::Cleanup`**, so you must use move semantics. Store it in a `std::unique_ptr<absl::Cleanup<T>>` if you need optional cleanup, or declare it as a direct member with `std::optional<absl::Cleanup<T>>` for deferred initialization. For containers, use `std::vector<std::unique_ptr<absl::Cleanup<T>>>` or wrap the cleanup in a move-only custom type.