# How to Use absl::Cleanup for RAII Without std::unique_ptr Overhead

> Learn how to use absl::Cleanup for RAII in C++! Achieve zero overhead and avoid std::unique_ptr's costly indirections and allocations. Implement efficient resource management.

- Repository: [Abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)
- Tags: deep-dive
- Published: 2026-07-13

---

**`absl::Cleanup` implements the scope-guard idiom using inline stack storage for callable objects, eliminating heap allocation, pointer indirection, and reference counting to provide zero-overhead RAII compared to `std::unique_ptr`.**

The `absl::Cleanup` utility from the `abseil/abseil-cpp` repository offers a lightweight alternative to `std::unique_ptr` for resource cleanup scenarios that do not require heap management. Instead of dynamic allocation and deletion, `absl::Cleanup` stores the cleanup callable directly within an aligned character buffer inside the object, allowing the compiler to optimize the entire lifecycle to essentially zero cost.

## Zero-Allocation Storage Architecture

Unlike `std::unique_ptr`, which carries a `sizeof(void*)` overhead and requires dynamic memory, `absl::Cleanup` uses a compact internal storage mechanism defined in [`absl/cleanup/internal/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/internal/cleanup.h).

### Inline Aligned Buffer Implementation

At lines 89-92 of [`absl/cleanup/internal/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/internal/cleanup.h), the `Storage` class defines an aligned character buffer that holds the callable object without heap allocation:

```cpp
alignas(Callback) unsigned char callback_buffer_[sizeof(Callback)];

```

This **placement-new** technique stores the lambda or function object directly inside the `Cleanup` instance. Because the callable resides in automatic storage, destruction requires no `delete` call—the destructor simply executes the callback if it remains engaged.

### Move-Only Semantics

`absl::Cleanup` is explicitly **move-only**, mirroring the ownership semantics of `std::unique_ptr` but without the pointer indirection. In [`absl/cleanup/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/cleanup.h) (lines 96-100), the class declares:

```cpp
Cleanup(Cleanup&& other) = default;
Cleanup(const Cleanup&) = delete;
Cleanup& operator=(const Cleanup&) = delete;

```

This ensures that only one instance owns the cleanup responsibility at a time, preventing double-execution while maintaining zero overhead through simple member-wise moves of the inline buffer.

## Using absl::Cleanup in Practice

The following patterns demonstrate how to replace `std::unique_ptr` custom deleters with zero-overhead scope guards.

### Basic Scope Guard

Replace `std::unique_ptr<void, decltype(&fclose)>` with a stack-based guard:

```cpp
#include <cstdio>
#include "absl/cleanup/cleanup.h"

void ProcessFile(const char* path) {
  FILE* f = std::fopen(path, "r");
  if (!f) return;

  absl::Cleanup closer = [f] { std::fclose(f); };

  char buffer[256];
  if (std::fread(buffer, 1, sizeof(buffer), f) == 0) return;
  // fclose executes automatically when closer goes out of scope
}

```

### Explicit Early Invocation

Call the cleanup manually before scope exit using the rvalue-qualified `Invoke()` method:

```cpp
absl::Cleanup cleanup = [] { std::cout << "Leaving scope\n"; };
if (need_early_exit) {
  std::move(cleanup).Invoke();  // Runs now; destructor does nothing
}

```

### Cancelling Cleanup

Prevent execution entirely using the `Cancel()` method:

```cpp
absl::Cleanup cleanup = [] { release_resource(); };
if (resource_already_released) {
  std::move(cleanup).Cancel();  // No-op on destruction
}

```

### C++11 Compatibility

For environments requiring C++11 compatibility, use `absl::MakeCleanup`:

```cpp
auto guard = absl::MakeCleanup([] { delete ptr; });

```

This factory function, defined in [`absl/cleanup/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/cleanup.h), provides identical zero-overhead guarantees while supporting older standards that lack class template argument deduction.

## Implementation Deep Dive

Understanding the source mechanics in `abseil/abseil-cpp` explains how the zero-overhead guarantee is enforced.

### Template Argument Deduction

To prevent explicit template specialization errors, `absl::Cleanup` instantiation relies on deduction guides defined in [`absl/cleanup/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/cleanup.h) (lines 85-108 and 120-130). The template signature:

```cpp
template <typename Arg, typename Callback = void()>
class Cleanup;

```

Works with the `MakeCleanup` factory to deduce callable types automatically, ensuring the inline buffer size matches exactly the stored lambda.

### Engaged State Management

The `Cancel()` and `Invoke()` methods use ref-qualified signatures (`&&`) to ensure they are called only on rvalues, as seen in [`absl/cleanup/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/cleanup.h) (lines 98-107):

```cpp
void Cancel() && {
  absl::base_internal::HardeningAssert(storage_.IsCallbackEngaged());
  storage_.DestroyCallback();
}
void Invoke() && {
  absl::base_internal::HardeningAssert(storage_.IsCallbackEngaged());
  storage_.InvokeCallback();
  storage_.DestroyCallback();
}

```

These methods clear the engagement flag, guaranteeing the destructor (lines 109-114) skips execution:

```cpp
~Cleanup() {
  if (storage_.IsCallbackEngaged()) {
    storage_.InvokeCallback();
    storage_.DestroyCallback();
  }
}

```

### Hardening Assertions

Safety checks use `absl::base_internal::HardeningAssert` (lines 99-100), a lightweight assertion that typically compiles to nothing in optimized builds while catching logic errors in debug mode. This adds no runtime cost to production binaries.

## Summary

- **`absl::Cleanup`** provides zero-overhead RAII by storing callables in an inline aligned buffer at [`absl/cleanup/internal/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/internal/cleanup.h) lines 89-92 instead of heap memory.
- The class is **move-only** (no copies) to prevent double-execution, implemented in [`absl/cleanup/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/cleanup.h) lines 96-100.
- **`Cancel()`** and **`Invoke()`** methods support explicit control and are ref-qualified to ensure correct usage.
- The implementation uses **placement-new** in automatic storage, eliminating the `sizeof(void*)` overhead and dynamic allocation costs of `std::unique_ptr`.
- **Hardening asserts** provide debug safety without impacting release performance.

## Frequently Asked Questions

### How does absl::Cleanup avoid heap allocation while std::unique_ptr requires it?

`std::unique_ptr` manages objects through dynamic memory, requiring allocation overhead and pointer indirection. In contrast, `absl::Cleanup` stores the callable directly in an `alignas(Callback) unsigned char` buffer defined in [`absl/cleanup/internal/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/internal/cleanup.h) (lines 89-92), keeping everything on the stack with no allocation.

### When should I use absl::Cleanup instead of std::unique_ptr?

Use `absl::Cleanup` when you need to execute a function at scope exit but do not own a heap-allocated object. It is ideal for C-style resource handles like `FILE*` or arbitrary cleanup logic. `std::unique_ptr` remains appropriate when you need to transfer ownership of heap memory.

### Can I cancel an absl::Cleanup after creating it?

Yes. Call `std::move(cleanup).Cancel()` to disengage the callback. According to the implementation in [`absl/cleanup/cleanup.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/cleanup/cleanup.h) (lines 98-107), this destroys the stored callable and clears the engagement flag, ensuring the destructor performs no action.

### Is absl::Cleanup compatible with C++11?

Yes. While `absl::Cleanup` supports C++17 class template argument deduction, C++11 code can use `absl::MakeCleanup()` to achieve the same zero-overhead behavior. Both approaches provide identical guarantees against dynamic allocation.