---
title: Hazard Pointer Synchronous Reclamation
document: P3427R4
date: 2026-05-11
audience: LEWG Library Evolution
reply-to:
  - "Maged Michael"
  - "Michael Wong"
  - "Paul McKenney"
  - "Mark de Wever"
paper-type: proposal
---

Document number: P3427R4 Date: 2026-05-11 Project: Programming Language C++, LEWG Authors: Maged M. Michael, Michael Wong, Paul McKenney, Mark de Wever Email: [maged.michael@gmail.com](mailto:maged.michael@acm.org), [fraggamuffin@gmail.com](mailto:fraggamuffin@gmail.com), [paulmck@kernel.org](mailto:paulmck@kernel.org), [koraq@xs4all.nl](mailto:koraq@xs4all.nl)

# Hazard Pointer Synchronous Reclamation

##### 

## Introduction

## Introduction

This paper proposes extending the C++26 hazard pointer interface to support synchronous reclamation. This revision, P3427R4, revises R3 by following LEWG Croydon 2026 feedback.

Hazard pointer interface from P2530R3 merged into the working draft (N5008):

:::wording-add

<ins>template <class T, class</ins> D = default_delete<T>> <ins>class</ins> hazard_pointer_obj_base { <ins>public</ins>: <ins>void retire(D d = D()) noexcept</ins>; <ins>protected</ins>: hazard_pointer_obj_base() = <ins>default</ins>; hazard_pointer_obj_base(<ins>const hazard_pointer_obj_base&) = default</ins>; hazard_pointer_obj_base(hazard_pointer_obj_base&&) = <ins>default</ins>; hazard_pointer_obj_base& <ins>operator=(const hazard_pointer_obj_base&) = default</ins>; hazard_pointer_obj_base& <ins>operator=(hazard_pointer_obj_base&&) = default</ins>; ~hazard_pointer_obj_base() = <ins>default</ins>; <ins>private</ins>: D deleter ; // exposition only }; <ins>class</ins> hazard_pointer { <ins>public</ins>: hazard_pointer() <ins>noexcept</ins>; hazard_pointer(hazard_pointer&&) <ins>noexcept</ins>; hazard_pointer& <ins>operator=(hazard_pointer&&) noexcept</ins>; ~hazard_pointer(); [[nodiscard]] <ins>bool empty() const noexcept</ins>; <ins>template <class T> T* protect(const atomic<T*>& src) noexcept</ins>; <ins>template <class T> bool try_protect(T*& ptr, const atomic<T*>& src) noexcept</ins>; <ins>template <class T> void reset_protection(const T* ptr) noexcept</ins>; <ins>void reset_protection(nullptr_t = nullptr) noexcept</ins>; <ins>void swap(hazard_pointer&) noexcept</ins>; }; hazard_pointer make_hazard_pointer(); <ins>void swap(hazard_pointer&, hazard_pointer&) noexcept</ins>;

:::

## Motivation

This paper proposes supporting object cohorts due to the importance of synchronous reclamation for general purpose usability. For example, a concurrent hash map that uses hazard pointers is more generally usable if it allows arbitrary key and value types rather than only types without dependence on resources with independent lifetimes.

## Implementation and Use Experience

Object cohorts have been part of the Folly open-source library (under the name `hazptr_obj_cohort`) and in heavy use in production since 2018. (See CppCon 2021 *[Hazard Pointer Synchronous reclamation beyond](https://www.youtube.com/watch?v=lsy8RRq2hHM)* *[Concurrency TS2](https://www.youtube.com/watch?v=lsy8RRq2hHM)* for details about the evolution of support for synchronous reclamation in Folly).

### Synchronous Reclamation

The P2530R3 C++26 hazard pointer interface supports only asynchronous reclamation which does not guarantee the timing of the reclamation of protectable objects that are no longer protected. As a result, hazard pointer users must guarantee separately that the deleters of such objects do not depends on resources that may become subsequently unavailable. Support for synchronous reclamation allows users to synchronously induce and wait for the reclamation of unprotected objects.

#### Global Cleanup

A straightforward albeit inefficient solution to this problem is global cleanup, which guarantees the completion of deleters of all unprotected retired objects. This involves synchronously checking all retired objects against all hazard pointers. The main drawback of the global cleanup approach is its high overhead that makes it impractical to use. For example, it may be useful to include global cleanup in the destructor of a generic container library. However, the prohibitive overhead of global cleanup makes that impractical for many use cases of such a container. Another drawback of the global cleanup approach is that it adds overhead to the hazard pointer implementation even when users never use this feature (e.g., would require synchronization on thread local private buffers of retired objects that would otherwise be unnecessary). We do not recommend the standardization of the global cleanup approach due to these drawbacks and the lack of production experience of the necessity of such approach in contrast to the more efficient approach discussed in the following section.

### Object Cohorts

An alternative approach is the use of object cohorts, which are sets of protectable objects. Object cohorts support synchronous reclamation by guaranteeing that **all the deleters of the object cohort members are completed before the completion of the** **cohorts** destructor. The main advantage of the object cohort approach is its performance. While its guarantees are weaker and less flexible than global cleanup, its efficiency enables users to use it in performance sensitive cases where the cost of global cleanup may be impractical. It strikes a better balance between practicality and performance. Therefore, we recommend object cohorts for standardization.

## Proposed Wording

Edit **17.3.2** **[version.syn] p2** as follows:

:::wording

__cpp_lib_hazard_pointer <del>202306L</del> <ins>202XXXXL</ins> [Editor's note: The value will be set to the date of the meeting where this paper is approved for the working draft.]

:::

Edit **32.11.3.1 [saferecl.hp.general] p4** as follows:

:::wording-add

An object x of hazard-protectable type T is retired with a deleter of type D when <ins>either</ins> the member function hazard_pointer_obj_base<T, D>::retire <ins>or the member function hazard_pointer_obj_base<T,</ins> <ins>D>::retire_to_cohort</ins> is invoked on x. Any given object x shall be retired at most once.

:::

Edit **32.11.3.2 [hazard.pointer.syn] p4** as follows:

:::wording-add

namespace std { <ins>// [saferecl.hp.cohort], class hazard_pointer_cohort</ins> <ins>class hazard_pointer_cohort;</ins> }

:::

Add **32.11.3.x** [hazard.pointer.cohort]:

```cpp
struct __hp_obj_base { }; // exposition only 

class hazard_pointer_cohort {
public: 
  hazard_pointer_cohort() noexcept = default;
  hazard_pointer_cohort(const hazard_pointer_cohort&) = delete;
  hazard_pointer_cohort(hazard_pointer_cohort&&) = delete;
  hazard_pointer_cohort& operator=(const hazard_pointer_cohort&) = delete;
  hazard_pointer_cohort& operator=(hazard_pointer_cohort&&) = delete;
  ~hazard_pointer_cohort(); 
private: 
  /* set-of-retired-objects */ members_; // exposition only
}; 
```

A *hazard-cohort* is a set of hazard-protectable objects. An object of type `hazard_pointer_cohort` is a hazard-cohort. Each element of `members_` is a pointer to an `__hp_obj_base` subobject of a retired object. `~hazard_pointer_cohort();` *Effects:* Reclaims all not-yet-reclaimed elements of `members_`. *Synchronization:* The completion of the deleter for each reclaimed object synchronizes with the return from this destructor. [*Note:* Elements of `members_` may have been reclaimed before the destruction of `*this`. -- *End* Note] [*Note:* This destructor reclaims all not-yet-reclaimed elements of `members_` regardless of whether or not these objects are possibly-reclaimable. -- *End* Note]

Edit **32.11.3.3 [saferecl.hp.base]** as follows:

:::wording-add

template <class T, class D = default_delete<T>> class hazard_pointer_obj_base : private __hp_obj_base { // expositional inheritance public: <ins>void retire_to_cohort(hazard_pointer_cohort& c, D d = D()) noexcept;</ins> }; void retire_to_cohort(hazard_pointer_cohort& c, D d = D()) noexcept; Preconditions: *this is a base class subobject of an object x of type T. x is not retired. Move-assigning d to deleter does not exit via an exception. Effects: Move-assigns d to deleter, thereby setting it as the deleter of x, then retires x. May reclaim possibly-reclaimable elements of c.members_. Postcondition: x is an element of c.members_.

:::

Add the following to **32.11.3.4.4** **[saferecl.hp.holder.nonmem]**:

`void hazard_pointer_try_reclamation() noexcept;` *Effects:* May reclaim possibly-reclaimable objects.

## Usage Example

P2530R3 C++26 (Asynchronous Reclamation Only) Cohort-Based Synchronous Reclamation

:::wording-add

/// Library <ins>template <class T> class</ins> Container { <ins>class</ins> Obj : hazard_pointer_obj_base<Obj> { T data; /* etc */ }; <ins>void</ins> insert(T data) { Obj* obj = <ins>new</ins> Obj(data); /* Insert obj in container */ } <ins>void</ins> erase(Args args) { Obj* obj = find(args); /* Remove obj from container */ executor_.add([] { obj->retire(); }); } }; /// User <ins>class</ins> A { // Deleter cannot depend on resources // with independent lifetime. ~A(); }; { Container<A> container; container.insert(a); container.erase(a); } // Obj containing 'a' may be not deleted // yet. /// Library <ins>template <class T> class</ins> Container { <ins>class</ins> Obj : hazard_pointer_obj_base<Obj> { T data; /* etc */ }; hazard_pointer_cohort cohort_; <ins>void</ins> insert(T data) { Obj* obj = <ins>new</ins> Obj(data); /* Insert obj in container */ } <ins>void</ins> erase(Args args) { Obj* obj = find(args); /* Remove obj from container */ obj->retire_to_cohort(cohort_); executor_.add([] { hazard_pointer_try_reclamation(); }); } }; /// User <ins>class</ins> B { // Deleter may depend on resources // with independent lifetime. ~B() { use_resource_XYZ(); } }; make_resource_XYZ(); { Container<B> container; container.insert(b); container.erase(b); } // Obj containing 'b' was deleted. destroy_resource_XYZ();

:::

In the above example:

* In the code snippet on the left side, the removed object is retired asynchronously by submitting the
retirement code to a dedicated thread pool (details not shown) to be executed asynchronously. Doing
so avoids burdening the current thread with potentially performing amortized reclamation of tens of
thousands of possibly unrelated retired objects, because calling `retire` may trigger amortized
asynchronous reclamation.

* In contrast, the code snippet on the right side, retirement to the cohort must be executed synchronously
in order for the object to be included as intended in synchronous reclamation of the associated cohort.
Therefore, the call to `retire_to_cohort` must be synchronous. But in order not to burden worker
threads with amortized asynchronous reclamation, `retire_to_cohort` does not try to perform
asynchronous reclamation. Instead, if desired, the current thread may submit an asynchronous task to
a dedicated thread pool to try to perform asynchronous reclamation (which attempts to reclaim
unprotected retired objects, both cohort-associated and not).

## Separating Cohort Object Retirement from Asynchronous Reclamation

### 

If a cohort is long-lived, large numbers (e.g., billions) of objects may be retired to it. If such objects are not included in asynchronous reclamation, they would remain not reclaimed. Therefore, cohort objects need to be included in asynchronous reclamation. Cohort Object Retirement Must Be Synchronous

In order for an object to be included in synchronous reclamation in association with a cohort, the object must be retired to the cohort. Therefore, cohort object retirement needs to be synchronous. Why Doesn't `retire_to_cohort` Try to Invoke Asynchronous Reclamation Implicitly?

As indicated above cohort-object retirement must be synchronous. However, it is often desirable to invoke asynchronous reclamation asynchronously in order to avoid burdening the thread retiring the object (typically a worker thread) with reclaiming a large number (e.g., tens of thousands) of (possibly unrelated) retired objects. In contrast, the retirement of non-cohort objects (using `retire`) by convention may implicitly invoke asynchronous reclamation. If desired, a user may retire a non-cohort object `obj` asynchronously to avoid inline asynchronous reclamation as follows (assuming an executor associated with a separate execution resource):

`executor_.add([obj] { obj->retire(); });` However, the retirement of a cohort object needs to be synchronous, as mentioned above. Therefore a free function hazard_pointer_try_reclamation() is added, so that users can write the cohort equivalent to the above non-cohort code snippet as follows:

```cpp
obj->retire_to_cohort(cohort_); 
executor_.add([] { hazard_pointer_try_reclamation(); }); 
```

### History

The Varna 2023 plenary voted in favor of including hazard pointers in the C++26 standard library ([2023-06 LWG Motion 7] P2530R3 Hazard Pointers for C++26). P3135R1 was presented to SG1 in Tokyo 2024, reviewing potential extensions of the P2530R3 C++26 interface, and proposing two of those for inclusion in the standard library. The proposal for extending the P2530R3 C++26 interface to support synchronous reclamation was voted on by SG1 with unanimous consent:

P3427R0 was a follow up on P3135R1, focusing on extending the P2530R3 C++26 hazard pointer interface to support synchronous reclamation, revised to take into account the feedback from SG1:

* Associate a hazard-protectable object with a cohort at the time of its call to retire().

R1

R1 added draft wording to R0. D3427R1 was reviewed by SG1 in Wroclaw 2024 and SG1 voted with unanimous consent to forward (D)P3427R1 to LEWG for C++26 with feedback:

```cpp
Forward (D)P3427R1 to LEWG for C++26 with notes: 
* The word 'asynchronous' doesn't belong in the name of the free function 
```

R2 revises R1 to follow the feedback from SG1 and for review by LEWG. It makes the following changes from R1:

* Change `hazard_pointer_asynchronous_reclamation` to `hazard_pointer_try_reclamation`.

* Added "May reclaim possibly-reclaimable members of `c`." to the wording of `reclaim to cohort`.

R3

R3 revises R2 in response to feedback from LEWG in Croydon 2026, by merging Proposed Interface with Draft Wording, adding an exposition-only container to hazard_pointer_cohort, and using the exposition-only member in wording. Otherwise, R3 is identical to R2. LEWG reviewed R2 and the updated R3 wording. LEWG voted to approve the design as in R2 (same as R3) with feedback:

```cpp
Approve the design of "P3427R2 Hazard Pointer Synchronous Reclamation" with the 
discussed changes. 
ACTIONs for P3427R3: 
```

* `Polish the wording (preferably with a review from a wording expert from LWG)`
`based on what was presented in the meeting and publish an updated R4.`

* `Bump the feature test macro.`

R4 revises R3 and applies feedback from LEWG in Croydon 2026, by polishing wording and including feature test macro bump.

## References

* [P2530R3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2530r3.pdf): Hazard Pointers for C++26 (2023-03-02).
* [P3135R1](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3135r1.pdf): Hazard Pointer Extensions (2024-04-12).
* [Folly](https://github.com/facebook/folly): Facebook Open-source Library.
* CppCon 2021: *[Hazard Pointer Synchronous reclamation beyond Concurrency TS2](https://www.youtube.com/watch?v=lsy8RRq2hHM)*
