I want to give full disclosure on AI usage for transparency. - implementation of hazard pointer: Used AI as a reviewer and debug helper. - test code: Heavily relied on AI for initial test code generation. - libstdc++ infrastructure: Used AI to help understanding code infrastructure - mailinglist: Used AI to write summaries of my patches for the mailinglist as I did not have any experience with this channel before. Looking back they seem to verbose and extensive (compared to other mails).
On Mon, Jun 8, 2026 at 10:41 PM Paul Xi Cao <[email protected]> wrote: > > Cover the full C++26 std::hazard_pointer API surface with DejaGnu > tests under 30_threads/hazard_pointer/. > > Functional tests (one file per API / concern): > * make_hazard_pointer.cc -- factory, slot accounting, pool growth. > * ctor.cc -- default ctor, move ctor, destructor, slot accounting. > * empty.cc -- empty() across every state transition. > * move_assign.cc -- transfers ownership, releases prior slot, self- > assignment is a no-op. > * swap.cc -- member swap, free swap, protection follows the slot. > * protect.cc -- returns current value, null source, slot ownership, > re-protect updates the slot. > * try_protect.cc -- success / failure return, ptr output, agreement > with protect() on a stable pointer. > * reset_protection.cc -- both overloads, idempotence, typed-pointer > overload publishes a hazard directly. > * retire.cc -- single retire, multiple retires, threshold auto- > synchronize fires when retired count exceeds 2 * active slots. > * custom_deleter.cc -- functor deleter invoked, default-constructed > deleter, deleter suppressed while protected. > > Compile-time property tests: > * noexcept.cc -- static_asserts for every noexcept-annotated API. > * type_constraints.cc -- _Protectable concept for derived / non- > derived types, retire() deleter constraints. > > Multi-threaded tests: > * concurrent.cc -- readers + writer, multi-writer multi-reader, > high-contention; run under TSan to verify no data races and no > use-after-free. > * thread_exit.cc -- ~_RetireListNode drains the retire list on > thread exit; protected survivors offload to the orphan list. > > Negative compile tests (dg-error + dg-prune-output for cascade > diagnostics past the static_assert / -Werror=unused-result): > * protect_neg.cc -- protect<int>() and protect<NonHazard>() > rejected by the _Protectable concept. > * retire_neg.cc -- retire() rejected when hazard_pointer_obj_base > is a private base. > * retire_virtual_neg.cc -- retire() rejected when > hazard_pointer_obj_base is a virtual base; gated on > __cpp_lib_is_virtual_base_of. > * nodiscard_neg.cc -- discarding the return of > make_hazard_pointer(), empty(), protect(), or try_protect() > trips -Werror=unused-result. > > All tests pass at -std=gnu++26 on x86_64-pc-linux-gnu. > > libstdc++-v3/ChangeLog: > > * testsuite/30_threads/hazard_pointer/concurrent.cc: New test. > * testsuite/30_threads/hazard_pointer/ctor.cc: New test. > * testsuite/30_threads/hazard_pointer/custom_deleter.cc: New test. > * testsuite/30_threads/hazard_pointer/empty.cc: New test. > * testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc: New > test. > * testsuite/30_threads/hazard_pointer/move_assign.cc: New test. > * testsuite/30_threads/hazard_pointer/nodiscard_neg.cc: New test. > * testsuite/30_threads/hazard_pointer/noexcept.cc: New test. > * testsuite/30_threads/hazard_pointer/protect.cc: New test. > * testsuite/30_threads/hazard_pointer/protect_neg.cc: New test. > * testsuite/30_threads/hazard_pointer/reset_protection.cc: New test. > * testsuite/30_threads/hazard_pointer/retire.cc: New test. > * testsuite/30_threads/hazard_pointer/retire_neg.cc: New test. > * testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc: New test. > * testsuite/30_threads/hazard_pointer/swap.cc: New test. > * testsuite/30_threads/hazard_pointer/thread_exit.cc: New test. > * testsuite/30_threads/hazard_pointer/try_protect.cc: New test. > * testsuite/30_threads/hazard_pointer/type_constraints.cc: New test. > --- > .../30_threads/hazard_pointer/concurrent.cc | 226 ++++++++++++++++++ > .../30_threads/hazard_pointer/ctor.cc | 102 ++++++++ > .../hazard_pointer/custom_deleter.cc | 126 ++++++++++ > .../30_threads/hazard_pointer/empty.cc | 105 ++++++++ > .../hazard_pointer/make_hazard_pointer.cc | 84 +++++++ > .../30_threads/hazard_pointer/move_assign.cc | 91 +++++++ > .../hazard_pointer/nodiscard_neg.cc | 58 +++++ > .../30_threads/hazard_pointer/noexcept.cc | 56 +++++ > .../30_threads/hazard_pointer/protect.cc | 96 ++++++++ > .../30_threads/hazard_pointer/protect_neg.cc | 43 ++++ > .../hazard_pointer/reset_protection.cc | 116 +++++++++ > .../30_threads/hazard_pointer/retire.cc | 119 +++++++++ > .../30_threads/hazard_pointer/retire_neg.cc | 39 +++ > .../hazard_pointer/retire_virtual_neg.cc | 40 ++++ > .../30_threads/hazard_pointer/swap.cc | 133 +++++++++++ > .../30_threads/hazard_pointer/thread_exit.cc | 98 ++++++++ > .../30_threads/hazard_pointer/try_protect.cc | 132 ++++++++++ > .../hazard_pointer/type_constraints.cc | 56 +++++ > 18 files changed, 1720 insertions(+) > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/concurrent.cc > create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/ctor.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/custom_deleter.cc > create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/empty.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/move_assign.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/nodiscard_neg.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/noexcept.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/protect.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/protect_neg.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/reset_protection.cc > create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/retire.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_neg.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc > create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/swap.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/thread_exit.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/try_protect.cc > create mode 100644 > libstdc++-v3/testsuite/30_threads/hazard_pointer/type_constraints.cc > > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/concurrent.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/concurrent.cc > new file mode 100644 > index 00000000000..097866eea7e > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/concurrent.cc > @@ -0,0 +1,226 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <chrono> > +#include <functional> > +#include <thread> > +#include <vector> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +namespace > +{ > + struct Node : std::hazard_pointer_obj_base<Node> > + { > + int value; > + explicit Node(int v) : value(v) {} > + }; > + > + void reader_fn(std::atomic<Node*>& shared, std::atomic<bool>& stop, > + std::atomic<int>& errors) > + { > + auto hp = std::make_hazard_pointer(); > + while (!stop.load(std::memory_order_acquire)) > + { > + const Node* const p = hp.protect(shared); > + if (p && p->value < 0) > + errors.fetch_add(1, std::memory_order_relaxed); > + hp.reset_protection(); > + } > + } > + > + void writer_fn(std::atomic<Node*>& shared, std::atomic<bool>& stop) > + { > + int counter = 0; > + while (!stop.load(std::memory_order_acquire)) > + { > + Node* next = new Node(++counter); > + Node* const old = shared.exchange(next, std::memory_order_seq_cst); > + if (old) > + old->retire(); > + } > + Node* final_ = shared.exchange(nullptr, std::memory_order_seq_cst); > + if (final_) > + final_->retire(); > + } > +} > + > +// Readers and one writer: no data race, no error from reading freed memory. > +void test01() > +{ > + std::atomic<Node*> shared{new Node(1)}; > + std::atomic<bool> stop{false}; > + std::atomic<int> errors{0}; > + > + constexpr int kReaders = 4; > + std::vector<std::thread> threads; > + threads.reserve(kReaders + 1); > + > + for (int i = 0; i < kReaders; ++i) > + threads.emplace_back(reader_fn, std::ref(shared), std::ref(stop), > + std::ref(errors)); > + threads.emplace_back(writer_fn, std::ref(shared), std::ref(stop)); > + > + std::this_thread::sleep_for(std::chrono::milliseconds(200)); > + stop.store(true, std::memory_order_release); > + for (auto& t : threads) > + t.join(); > + > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( errors.load() == 0 ); > +} > + > +// protect() sees the latest value across stores. > +void test02() > +{ > + auto hp = std::make_hazard_pointer(); > + Node a{10}, b{20}; > + std::atomic<Node*> src{&a}; > + > + const Node* const p1 = hp.protect(src); > + VERIFY( p1 == &a ); > + > + src.store(&b, std::memory_order_release); > + const Node* const p2 = hp.protect(src); > + VERIFY( p2 == &b ); > +} > + > +// Multiple writers, multiple readers: no data race. > +void test03() > +{ > + constexpr int kReaders = 4; > + constexpr int kWriters = 4; > + > + std::vector<std::atomic<Node*>> srcs(kWriters); > + for (auto& s : srcs) > + s.store(new Node(1), std::memory_order_relaxed); > + > + std::atomic<bool> stop{false}; > + std::atomic<int> errors{0}; > + > + auto multi_reader = [&] { > + std::vector<std::hazard_pointer> hps; > + hps.reserve(kWriters); > + for (int i = 0; i < kWriters; ++i) > + hps.emplace_back(std::make_hazard_pointer()); > + while (!stop.load(std::memory_order_acquire)) > + for (int i = 0; i < kWriters; ++i) > + { > + const Node* const p = hps[i].protect(srcs[i]); > + if (p && p->value < 0) > + errors.fetch_add(1, std::memory_order_relaxed); > + hps[i].reset_protection(); > + } > + }; > + > + auto single_writer = [&](int idx) { > + int counter = 1; > + while (!stop.load(std::memory_order_acquire)) > + { > + Node* next = new Node(++counter); > + Node* old = srcs[idx].exchange(next, std::memory_order_seq_cst); > + if (old) > + old->retire(); > + } > + Node* final_ = srcs[idx].exchange(nullptr, std::memory_order_seq_cst); > + if (final_) > + final_->retire(); > + }; > + > + std::vector<std::thread> threads; > + threads.reserve(kReaders + kWriters); > + for (int i = 0; i < kReaders; ++i) > + threads.emplace_back(multi_reader); > + for (int i = 0; i < kWriters; ++i) > + threads.emplace_back(single_writer, i); > + > + std::this_thread::sleep_for(std::chrono::milliseconds(200)); > + stop.store(true, std::memory_order_release); > + for (auto& t : threads) > + t.join(); > + > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( errors.load() == 0 ); > +} > + > +// Retire from many threads: all reclaimed by thread-exit drain. > +void test04() > +{ > + constexpr int kThreads = 8; > + constexpr int kObjectsPerThread = 10; > + std::atomic<int> total_deleted{0}; > + > + struct CountedNode : std::hazard_pointer_obj_base<CountedNode> > + { > + std::atomic<int>& counter; > + explicit CountedNode(std::atomic<int>& c) : counter(c) {} > + ~CountedNode() { counter.fetch_add(1, std::memory_order_relaxed); } > + }; > + > + std::vector<std::thread> threads; > + threads.reserve(kThreads); > + for (int i = 0; i < kThreads; ++i) > + threads.emplace_back([&] { > + for (int j = 0; j < kObjectsPerThread; ++j) > + (new CountedNode(total_deleted))->retire(); > + }); > + for (auto& t : threads) > + t.join(); > + > + VERIFY( total_deleted.load() == kThreads * kObjectsPerThread ); > +} > + > +// High contention: readers + writer over longer duration. > +void test05() > +{ > + constexpr int kReaders = 6; > + std::atomic<Node*> shared{new Node(42)}; > + std::atomic<bool> stop{false}; > + std::atomic<int> errors{0}; > + > + std::vector<std::thread> threads; > + threads.reserve(kReaders + 1); > + > + for (int i = 0; i < kReaders; ++i) > + threads.emplace_back(reader_fn, std::ref(shared), std::ref(stop), > + std::ref(errors)); > + threads.emplace_back(writer_fn, std::ref(shared), std::ref(stop)); > + > + std::this_thread::sleep_for(std::chrono::milliseconds(300)); > + stop.store(true, std::memory_order_release); > + for (auto& t : threads) > + t.join(); > + > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( errors.load() == 0 ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > +} > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/ctor.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/ctor.cc > new file mode 100644 > index 00000000000..791b3cdbadc > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/ctor.cc > @@ -0,0 +1,102 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <utility> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +// Default-constructed handle is empty. > +void test01() > +{ > + const std::hazard_pointer hp; > + VERIFY( hp.empty() ); > +} > + > +// Default-constructed handle does not increment active slot count. > +void test02() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + const std::hazard_pointer hp; > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before ); > +} > + > +// Move ctor transfers slot ownership: count unchanged. > +void test03() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + auto a = std::make_hazard_pointer(); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 ); > + const std::hazard_pointer b = std::move(a); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 ); > +} > + > +// Move ctor: source becomes empty. > +void test04() > +{ > + auto a = std::make_hazard_pointer(); > + const std::hazard_pointer b = std::move(a); > + VERIFY( a.empty() ); > + VERIFY( !b.empty() ); > +} > + > +// Destructor releases slot. > +void test05() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + { > + auto hp = std::make_hazard_pointer(); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 ); > + } > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before ); > +} > + > +// Destructor on default-constructed handle is a no-op. > +void test06() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + { > + const std::hazard_pointer hp; > + } > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before ); > +} > + > +// Multiple hazard_pointers are distinct slots. > +void test07() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + auto a = std::make_hazard_pointer(); > + auto b = std::make_hazard_pointer(); > + auto c = std::make_hazard_pointer(); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 3 ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > + test06(); > + test07(); > +} > diff --git > a/libstdc++-v3/testsuite/30_threads/hazard_pointer/custom_deleter.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/custom_deleter.cc > new file mode 100644 > index 00000000000..36d143945a6 > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/custom_deleter.cc > @@ -0,0 +1,126 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +namespace > +{ > + struct LoggingNode; > + > + struct LoggingDeleter > + { > + int* delete_count = nullptr; > + LoggingDeleter() = default; > + explicit LoggingDeleter(int* c) : delete_count(c) {} > + void operator()(LoggingNode* p) const; > + }; > + > + struct LoggingNode : std::hazard_pointer_obj_base<LoggingNode, > LoggingDeleter> {}; > + > + inline void LoggingDeleter::operator()(LoggingNode* p) const > + { > + if (delete_count) > + ++(*delete_count); > + delete p; > + } > + > + struct TrackedNode : std::hazard_pointer_obj_base<TrackedNode> > + { > + explicit TrackedNode(int& c) : counter(c) {} > + ~TrackedNode() { ++counter; } > + int& counter; > + }; > +} > + > +// Default deleter calls delete. > +void test01() > +{ > + int dtor_count = 0; > + auto* node = new TrackedNode(dtor_count); > + node->retire(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 1 ); > +} > + > +// Functor deleter is invoked. > +void test02() > +{ > + int delete_count = 0; > + auto* node = new LoggingNode(); > + node->retire(LoggingDeleter{&delete_count}); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( delete_count == 1 ); > +} > + > +// Functor deleter not called while protected. > +void test03() > +{ > + int delete_count = 0; > + auto* node = new LoggingNode(); > + const std::atomic<LoggingNode*> src{node}; > + > + auto hp = std::make_hazard_pointer(); > + (void)hp.protect(src); > + node->retire(LoggingDeleter{&delete_count}); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( delete_count == 0 ); > + > + hp.reset_protection(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( delete_count == 1 ); > +} > + > +// retire() with no arg uses default-constructed D. > +void test04() > +{ > + int dtor_count = 0; > + auto* node = new TrackedNode(dtor_count); > + node->retire(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 1 ); > +} > + > +// Multiple nodes with custom deleter all invoked. > +void test05() > +{ > + constexpr int kCount = 5; > + int delete_count = 0; > + for (int i = 0; i < kCount; ++i) > + { > + auto* node = new LoggingNode(); > + node->retire(LoggingDeleter{&delete_count}); > + } > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( delete_count == kCount ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > +} > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/empty.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/empty.cc > new file mode 100644 > index 00000000000..afd53c8bd8a > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/empty.cc > @@ -0,0 +1,105 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <utility> > +#include <testsuite_hooks.h> > + > +namespace > +{ > + struct Node : std::hazard_pointer_obj_base<Node> {}; > +} > + > +void test01() > +{ > + const std::hazard_pointer hp; > + VERIFY( hp.empty() ); > +} > + > +void test02() > +{ > + auto hp = std::make_hazard_pointer(); > + VERIFY( !hp.empty() ); > +} > + > +// After move ctor: source empty, dest non-empty. > +void test03() > +{ > + auto a = std::make_hazard_pointer(); > + const std::hazard_pointer b = std::move(a); > + VERIFY( a.empty() ); > + VERIFY( !b.empty() ); > +} > + > +// After move-assign: source empty. > +void test04() > +{ > + auto a = std::make_hazard_pointer(); > + std::hazard_pointer b; > + b = std::move(a); > + VERIFY( a.empty() ); > + VERIFY( !b.empty() ); > +} > + > +// After protect(): still owns slot. > +void test05() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + const std::atomic<Node*> src{&x}; > + (void)hp.protect(src); > + VERIFY( !hp.empty() ); > +} > + > +// After reset_protection(): hazard cleared, slot kept. > +void test06() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + const std::atomic<Node*> src{&x}; > + (void)hp.protect(src); > + hp.reset_protection(); > + VERIFY( !hp.empty() ); > +} > + > +// After swap: ownership exchanged. > +void test07() > +{ > + auto a = std::make_hazard_pointer(); > + std::hazard_pointer b; > + VERIFY( !a.empty() ); > + VERIFY( b.empty() ); > + a.swap(b); > + VERIFY( a.empty() ); > + VERIFY( !b.empty() ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > + test06(); > + test07(); > +} > diff --git > a/libstdc++-v3/testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc > new file mode 100644 > index 00000000000..a726480dc63 > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc > @@ -0,0 +1,84 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <cstddef> > +#include <vector> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +// Returns non-empty handle. > +void test01() > +{ > + auto hp = std::make_hazard_pointer(); > + VERIFY( !hp.empty() ); > +} > + > +// Increments active slot count by 1. > +void test02() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + auto hp = std::make_hazard_pointer(); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 ); > +} > + > +// Multiple calls return distinct slots. > +void test03() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + auto a = std::make_hazard_pointer(); > + auto b = std::make_hazard_pointer(); > + auto c = std::make_hazard_pointer(); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 3 ); > +} > + > +// Slot reused after handle destruction. > +void test04() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + { > + auto hp = std::make_hazard_pointer(); > + } > + auto hp2 = std::make_hazard_pointer(); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 ); > +} > + > +// Pool grows beyond initial capacity (_S_initial_slots == 8). > +void test05() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + constexpr std::size_t kExtra = 8 + 4; > + std::vector<std::hazard_pointer> hps; > + hps.reserve(kExtra); > + for (std::size_t i = 0; i < kExtra; ++i) > + hps.emplace_back(std::make_hazard_pointer()); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + kExtra ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > +} > \ No newline at end of file > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/move_assign.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/move_assign.cc > new file mode 100644 > index 00000000000..7f4e40c1cda > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/move_assign.cc > @@ -0,0 +1,91 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <utility> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +// Transfers slot ownership. > +void test01() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + auto a = std::make_hazard_pointer(); > + std::hazard_pointer b; > + VERIFY( !a.empty() ); > + VERIFY( b.empty() ); > + b = std::move(a); > + VERIFY( a.empty() ); > + VERIFY( !b.empty() ); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 ); > +} > + > +// Source becomes empty after move-assign. > +void test02() > +{ > + auto a = std::make_hazard_pointer(); > + std::hazard_pointer b; > + b = std::move(a); > + VERIFY( a.empty() ); > +} > + > +// Destination releases prior slot. > +void test03() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + auto a = std::make_hazard_pointer(); > + auto b = std::make_hazard_pointer(); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 2 ); > + b = std::move(a); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 ); > +} > + > +// Self-assignment is a no-op. > +void test04() > +{ > + auto hp = std::make_hazard_pointer(); > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + auto& alias = hp; > + hp = std::move(alias); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before ); > + VERIFY( !hp.empty() ); > +} > + > +// Move-assign empty to empty: both stay empty, no slot change. > +void test05() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + std::hazard_pointer a; > + std::hazard_pointer b; > + b = std::move(a); > + VERIFY( b.empty() ); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > +} > diff --git > a/libstdc++-v3/testsuite/30_threads/hazard_pointer/nodiscard_neg.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/nodiscard_neg.cc > new file mode 100644 > index 00000000000..eb725da6790 > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/nodiscard_neg.cc > @@ -0,0 +1,58 @@ > +// { dg-do compile { target c++26 } } > +// { dg-additional-options "-pthread -Werror=unused-result" { target pthread > } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +// Negative tests: each [[nodiscard]] return discard must trip > +// -Werror=unused-result. > + > +#include <hazard_pointer> > +#include <atomic> > + > +struct Node : std::hazard_pointer_obj_base<Node> {}; > + > +void test_make_hazard_pointer() > +{ > + std::make_hazard_pointer(); // { dg-error "ignoring return value" } > +} > + > +void test_empty() > +{ > + auto hp = std::make_hazard_pointer(); > + hp.empty(); // { dg-error "ignoring return value" } > +} > + > +void test_protect() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + std::atomic<Node*> src{&x}; > + hp.protect(src); // { dg-error "ignoring return value" } > +} > + > +void test_try_protect() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + std::atomic<Node*> src{&x}; > + Node* ptr = &x; > + hp.try_protect(ptr, src); // { dg-error "ignoring return value" } > +} > + > +// { dg-prune-output "some warnings being treated as errors" } > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/noexcept.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/noexcept.cc > new file mode 100644 > index 00000000000..cc19bc309cd > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/noexcept.cc > @@ -0,0 +1,56 @@ > +// { dg-do compile { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <type_traits> > +#include <utility> > + > +namespace > +{ > + struct Node : std::hazard_pointer_obj_base<Node> {}; > + using HP = std::hazard_pointer; > + > + static_assert(std::is_nothrow_default_constructible_v<HP>); > + static_assert(std::is_nothrow_move_constructible_v<HP>); > + static_assert(std::is_nothrow_move_assignable_v<HP>); > + > + static_assert(noexcept(std::declval<const HP&>().empty())); > + > + static_assert(noexcept(std::declval<HP&>().protect( > + std::declval<std::atomic<Node*>&>()))); > + > + static_assert(noexcept(std::declval<HP&>().try_protect( > + std::declval<Node*&>(), > + std::declval<const std::atomic<Node*>&>()))); > + > + static_assert(noexcept(std::declval<HP&>().reset_protection())); > + static_assert(noexcept(std::declval<HP&>().reset_protection(nullptr))); > + > + static_assert(noexcept(std::declval<HP&>().reset_protection( > + std::declval<const Node*>()))); > + > + static_assert(noexcept(std::declval<HP&>().swap(std::declval<HP&>()))); > + > + static_assert(noexcept(std::swap(std::declval<HP&>(), > std::declval<HP&>()))); > + > + static_assert(noexcept(std::declval<Node&>().retire())); > +} > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect.cc > new file mode 100644 > index 00000000000..19f38292263 > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect.cc > @@ -0,0 +1,96 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <testsuite_hooks.h> > + > +namespace > +{ > + struct Node : std::hazard_pointer_obj_base<Node> > + { > + int value = 0; > + }; > +} > + > +// Returns current value of the atomic. > +void test01() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + x.value = 42; > + const std::atomic<Node*> src{&x}; > + const Node* const p = hp.protect(src); > + VERIFY( p == &x ); > + VERIFY( p->value == 42 ); > +} > + > +// Returns nullptr for null atomic. > +void test02() > +{ > + auto hp = std::make_hazard_pointer(); > + const std::atomic<Node*> src{nullptr}; > + VERIFY( hp.protect(src) == nullptr ); > +} > + > +// Slot not empty after protect. > +void test03() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + const std::atomic<Node*> src{&x}; > + (void)hp.protect(src); > + VERIFY( !hp.empty() ); > +} > + > +// protect() matches atomic load. > +void test04() > +{ > + auto hp = std::make_hazard_pointer(); > + Node vals[3]; > + vals[0].value = 1; > + vals[1].value = 2; > + vals[2].value = 3; > + const std::atomic<Node*> src{&vals[0]}; > + const Node* const p = hp.protect(src); > + VERIFY( p == src.load() ); > +} > + > +// Re-protect on the same handle updates the slot. > +void test05() > +{ > + auto hp = std::make_hazard_pointer(); > + Node a, b; > + std::atomic<Node*> src{&a}; > + (void)hp.protect(src); > + src.store(&b, std::memory_order_relaxed); > + const Node* const p = hp.protect(src); > + VERIFY( p == &b ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > +} > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect_neg.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect_neg.cc > new file mode 100644 > index 00000000000..07cd3f66d6b > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect_neg.cc > @@ -0,0 +1,43 @@ > +// { dg-do compile { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +// Negative tests: protect<T>() must be rejected when T is not _Protectable. > + > +#include <hazard_pointer> > +#include <atomic> > + > +struct Plain {}; > + > +void test_int() > +{ > + auto hp = std::make_hazard_pointer(); > + std::atomic<int*> src{nullptr}; > + (void)hp.protect(src); // int is not _Protectable > +} > + > +void test_nonhazard_class() > +{ > + auto hp = std::make_hazard_pointer(); > + std::atomic<Plain*> src{nullptr}; > + (void)hp.protect(src); // Plain has no _Obj_base_tag base > +} > + > +// { dg-error "hazard-protectable type" "" { target *-*-* } 0 } > diff --git > a/libstdc++-v3/testsuite/30_threads/hazard_pointer/reset_protection.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/reset_protection.cc > new file mode 100644 > index 00000000000..5dddef71611 > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/reset_protection.cc > @@ -0,0 +1,116 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +namespace > +{ > + struct Node : std::hazard_pointer_obj_base<Node> {}; > + > + struct Tracked : std::hazard_pointer_obj_base<Tracked> > + { > + explicit Tracked(int& c) : counter(c) {} > + ~Tracked() { ++counter; } > + int& counter; > + }; > +} > + > +// reset_protection clears hazard, slot still owned. > +void test01() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + const std::atomic<Node*> src{&x}; > + (void)hp.protect(src); > + hp.reset_protection(); > + VERIFY( !hp.empty() ); > +} > + > +// nullptr overload clears hazard. > +void test02() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + const std::atomic<Node*> src{&x}; > + (void)hp.protect(src); > + hp.reset_protection(nullptr); > + VERIFY( !hp.empty() ); > +} > + > +// After reset, object can be reclaimed. > +void test03() > +{ > + auto hp = std::make_hazard_pointer(); > + int dtor_count = 0; > + auto* obj = new Tracked(dtor_count); > + const std::atomic<Tracked*> src{obj}; > + (void)hp.protect(src); > + > + obj->retire(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 0 ); > + > + hp.reset_protection(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 1 ); > +} > + > +// Typed-pointer overload publishes a hazard. > +void test04() > +{ > + auto hp = std::make_hazard_pointer(); > + int dtor = 0; > + auto* obj = new Tracked(dtor); > + hp.reset_protection(obj); > + > + obj->retire(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor == 0 ); > + > + hp.reset_protection(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor == 1 ); > +} > + > +// Double reset is safe (idempotent). > +void test05() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + const std::atomic<Node*> src{&x}; > + (void)hp.protect(src); > + hp.reset_protection(); > + hp.reset_protection(); > + VERIFY( !hp.empty() ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > +} > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire.cc > new file mode 100644 > index 00000000000..afcc97f2ca2 > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire.cc > @@ -0,0 +1,119 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +namespace > +{ > + struct Tracked : std::hazard_pointer_obj_base<Tracked> > + { > + explicit Tracked(int& counter) : counter_(counter) {} > + ~Tracked() { ++counter_; } > + int& counter_; > + }; > +} > + > +// Retired object deleted after synchronize. > +void test01() > +{ > + // Hold one active slot so the auto-sync threshold (2 * active_count) >= 2, > + // ensuring the single retire below does NOT auto-sync. > + auto hp = std::make_hazard_pointer(); > + int dtor_count = 0; > + (new Tracked(dtor_count))->retire(); > + VERIFY( dtor_count == 0 ); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 1 ); > +} > + > +// Multiple retired objects all deleted on synchronize. > +void test02() > +{ > + int dtor_count = 0; > + for (int i = 0; i < 5; ++i) > + (new Tracked(dtor_count))->retire(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 5 ); > +} > + > +// Protected object survives synchronize. > +void test03() > +{ > + int dtor_count = 0; > + auto* obj = new Tracked(dtor_count); > + const std::atomic<Tracked*> src{obj}; > + > + auto hp = std::make_hazard_pointer(); > + const Tracked* const protected_obj = hp.protect(src); > + VERIFY( protected_obj == obj ); > + > + obj->retire(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 0 ); > + > + hp.reset_protection(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 1 ); > +} > + > +// Retire list grows before synchronize. > +void test04() > +{ > + auto hp = std::make_hazard_pointer(); > + const auto before = hpd::_S_default_domain()._M_retire_list_size(); > + int dtor_count = 0; > + (new Tracked(dtor_count))->retire(); > + (new Tracked(dtor_count))->retire(); > + VERIFY( hpd::_S_default_domain()._M_retire_list_size() == before + 2 ); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( hpd::_S_default_domain()._M_retire_list_size() == 0u ); > + VERIFY( dtor_count == 2 ); > +} > + > +// Threshold auto-triggers synchronize: 1 active slot -> threshold 2 -> 3 > retires trip it. > +void test05() > +{ > + auto hp = std::make_hazard_pointer(); > + int dummy = 0; > + Tracked x{dummy}; > + const std::atomic<Tracked*> src{&x}; > + const Tracked* const protected_x = hp.protect(src); > + VERIFY( protected_x == &x ); > + > + int dtor_count = 0; > + for (int i = 0; i < 3; ++i) > + (new Tracked(dtor_count))->retire(); > + > + VERIFY( dtor_count > 0 ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > +} > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_neg.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_neg.cc > new file mode 100644 > index 00000000000..9b1c952f3af > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_neg.cc > @@ -0,0 +1,39 @@ > +// { dg-do compile { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +// Negative test: retire() must be rejected when hazard_pointer_obj_base > +// is a private base of T (is_convertible_v<T*, base*> fails). > + > +#include <hazard_pointer> > + > +struct Node : private std::hazard_pointer_obj_base<Node> > +{ > + using hazard_pointer_obj_base::retire; > +}; > + > +void test() > +{ > + auto* p = new Node(); > + p->retire(); > +} > + > +// { dg-error "must be a public base of T" "" { target *-*-* } 0 } > +// { dg-prune-output "inaccessible base of" } > diff --git > a/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc > new file mode 100644 > index 00000000000..7275a7b3f10 > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc > @@ -0,0 +1,40 @@ > +// { dg-do compile { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +// Negative test: retire() must be rejected when hazard_pointer_obj_base > +// is a virtual base of T. Requires C++26 std::is_virtual_base_of. > + > +#include <hazard_pointer> > + > +#ifndef __cpp_lib_is_virtual_base_of > +# error "Requires __cpp_lib_is_virtual_base_of for this negative check" > +#endif > + > +struct Node : virtual std::hazard_pointer_obj_base<Node> {}; > + > +void test() > +{ > + auto* p = new Node(); > + p->retire(); > +} > + > +// { dg-error "must be a non-virtual base" "" { target *-*-* } 0 } > +// { dg-prune-output "cannot convert from pointer to base class" } > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/swap.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/swap.cc > new file mode 100644 > index 00000000000..2d516d0bfc7 > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/swap.cc > @@ -0,0 +1,133 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +namespace > +{ > + struct Tracked : std::hazard_pointer_obj_base<Tracked> > + { > + explicit Tracked(int& c) : counter(c) {} > + ~Tracked() { ++counter; } > + int& counter; > + }; > +} > + > +// Member swap exchanges non-empty and empty. > +void test01() > +{ > + auto a = std::make_hazard_pointer(); > + std::hazard_pointer b; > + VERIFY( !a.empty() ); > + VERIFY( b.empty() ); > + a.swap(b); > + VERIFY( a.empty() ); > + VERIFY( !b.empty() ); > +} > + > +// Member swap both non-empty: slot count unchanged. > +void test02() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + auto a = std::make_hazard_pointer(); > + auto b = std::make_hazard_pointer(); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 2 ); > + a.swap(b); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 2 ); > +} > + > +// After swap, the protecting slot moves to the other handle. > +void test03() > +{ > + auto hp1 = std::make_hazard_pointer(); > + auto hp2 = std::make_hazard_pointer(); > + > + int dtor = 0; > + auto* obj = new Tracked(dtor); > + const std::atomic<Tracked*> src{obj}; > + (void)hp1.protect(src); > + > + hp1.swap(hp2); > + > + obj->retire(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor == 0 ); > + > + hp2.reset_protection(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor == 1 ); > +} > + > +// Free swap exchanges slots. > +void test04() > +{ > + auto a = std::make_hazard_pointer(); > + std::hazard_pointer b; > + VERIFY( !a.empty() ); > + VERIFY( b.empty() ); > + std::swap(a, b); > + VERIFY( a.empty() ); > + VERIFY( !b.empty() ); > +} > + > +// Free swap both non-empty: count unchanged. > +void test05() > +{ > + const auto before = hpd::_S_default_domain()._M_active_slots(); > + auto a = std::make_hazard_pointer(); > + auto b = std::make_hazard_pointer(); > + std::swap(a, b); > + VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 2 ); > +} > + > +// Member self-swap is a no-op. > +void test06() > +{ > + auto hp = std::make_hazard_pointer(); > + VERIFY( !hp.empty() ); > + hp.swap(hp); > + VERIFY( !hp.empty() ); > +} > + > +// Free swap both empty: remains empty. > +void test07() > +{ > + std::hazard_pointer a; > + std::hazard_pointer b; > + std::swap(a, b); > + VERIFY( a.empty() ); > + VERIFY( b.empty() ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > + test06(); > + test07(); > +} > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/thread_exit.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/thread_exit.cc > new file mode 100644 > index 00000000000..cabf95eaeef > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/thread_exit.cc > @@ -0,0 +1,98 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <thread> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +namespace > +{ > + struct Tracked : std::hazard_pointer_obj_base<Tracked> > + { > + explicit Tracked(int& c) : counter(c) {} > + ~Tracked() { ++counter; } > + int& counter; > + }; > +} > + > +// Retired objects in a worker thread reclaimed on thread exit. > +void test01() > +{ > + int dtor_count = 0; > + std::thread t([&] { > + for (int i = 0; i < 3; ++i) > + (new Tracked(dtor_count))->retire(); > + }); > + t.join(); > + VERIFY( dtor_count == 3 ); > +} > + > +// Protected survivors offloaded to orphan list, reclaimed by later > synchronize. > +void test02() > +{ > + int dtor_count = 0; > + auto* obj = new Tracked(dtor_count); > + const std::atomic<Tracked*> src{obj}; > + > + auto hp = std::make_hazard_pointer(); > + (void)hp.protect(src); > + > + std::thread t([&] { > + obj->retire(); > + }); > + t.join(); > + > + VERIFY( dtor_count == 0 ); > + > + hp.reset_protection(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 1 ); > +} > + > +// Many threads each drain own retire list on exit. > +void test03() > +{ > + constexpr int kThreads = 4; > + constexpr int kObjectsPerThread = 5; > + int dtor_counts[kThreads] = {}; > + > + std::thread threads[kThreads]; > + for (int i = 0; i < kThreads; ++i) > + threads[i] = std::thread([&dtor_counts, i] { > + for (int j = 0; j < kObjectsPerThread; ++j) > + (new Tracked(dtor_counts[i]))->retire(); > + }); > + for (auto& t : threads) > + t.join(); > + > + for (int i = 0; i < kThreads; ++i) > + VERIFY( dtor_counts[i] == kObjectsPerThread ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > +} > diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/try_protect.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/try_protect.cc > new file mode 100644 > index 00000000000..4af3192a2ad > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/try_protect.cc > @@ -0,0 +1,132 @@ > +// { dg-do run { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <atomic> > +#include <testsuite_hooks.h> > + > +namespace hpd = std::__hazard_pointer; > + > +namespace > +{ > + struct Node : std::hazard_pointer_obj_base<Node> > + { > + int value = 0; > + }; > + > + struct Tracked : std::hazard_pointer_obj_base<Tracked> > + { > + explicit Tracked(int& c) : counter(c) {} > + ~Tracked() { ++counter; } > + int& counter; > + }; > +} > + > +// Returns true when pointer is stable. > +void test01() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + const std::atomic<Node*> src{&x}; > + Node* ptr = src.load(std::memory_order::relaxed); > + VERIFY( hp.try_protect(ptr, src) ); > +} > + > +// Sets ptr on success. > +void test02() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + x.value = 7; > + const std::atomic<Node*> src{&x}; > + Node* ptr = src.load(std::memory_order::relaxed); > + const bool ok = hp.try_protect(ptr, src); > + VERIFY( ok ); > + VERIFY( ptr == &x ); > +} > + > +// Returns true for null source. > +void test03() > +{ > + auto hp = std::make_hazard_pointer(); > + const std::atomic<Node*> src{nullptr}; > + Node* ptr = nullptr; > + VERIFY( hp.try_protect(ptr, src) ); > + VERIFY( ptr == nullptr ); > +} > + > +// Slot still owned after success. > +void test04() > +{ > + auto hp = std::make_hazard_pointer(); > + Node x; > + const std::atomic<Node*> src{&x}; > + Node* ptr = &x; > + (void)hp.try_protect(ptr, src); > + VERIFY( !hp.empty() ); > +} > + > +// Protected object not reclaimed after success. > +void test05() > +{ > + auto hp = std::make_hazard_pointer(); > + int dtor_count = 0; > + auto* obj = new Tracked(dtor_count); > + const std::atomic<Tracked*> src{obj}; > + Tracked* ptr = src.load(std::memory_order::relaxed); > + VERIFY( hp.try_protect(ptr, src) ); > + > + obj->retire(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 0 ); > + > + hp.reset_protection(); > + hpd::_S_default_domain()._M_synchronize(); > + VERIFY( dtor_count == 1 ); > +} > + > +// try_protect and protect agree on a stable pointer. > +void test06() > +{ > + Node x; > + const std::atomic<Node*> src{&x}; > + > + auto hp1 = std::make_hazard_pointer(); > + auto hp2 = std::make_hazard_pointer(); > + > + Node* ptr = src.load(std::memory_order::relaxed); > + const bool ok = hp1.try_protect(ptr, src); > + const Node* const via_protect = hp2.protect(src); > + > + VERIFY( ok ); > + VERIFY( ptr == via_protect ); > + VERIFY( ptr == &x ); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + test04(); > + test05(); > + test06(); > +} > diff --git > a/libstdc++-v3/testsuite/30_threads/hazard_pointer/type_constraints.cc > b/libstdc++-v3/testsuite/30_threads/hazard_pointer/type_constraints.cc > new file mode 100644 > index 00000000000..60040bc3c71 > --- /dev/null > +++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/type_constraints.cc > @@ -0,0 +1,56 @@ > +// { dg-do compile { target c++26 } } > +// { dg-additional-options "-pthread" { target pthread } } > +// { dg-require-gthreads "" } > + > +// Copyright (C) 2026 Free Software Foundation, Inc. > +// > +// This file is part of the GNU ISO C++ Library. This library is free > +// software; you can redistribute it and/or modify it under the > +// terms of the GNU General Public License as published by the > +// Free Software Foundation; either version 3, or (at your option) > +// any later version. > + > +// This library is distributed in the hope that it will be useful, > +// but WITHOUT ANY WARRANTY; without even the implied warranty of > +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +// GNU General Public License for more details. > + > +// You should have received a copy of the GNU General Public License along > +// with this library; see the file COPYING3. If not see > +// <http://www.gnu.org/licenses/>. > + > +#include <hazard_pointer> > +#include <memory> > +#include <string> > +#include <type_traits> > + > +namespace > +{ > + struct SimpleNode : std::hazard_pointer_obj_base<SimpleNode> {}; > + struct CustomDelNode > + : std::hazard_pointer_obj_base<CustomDelNode, > + std::default_delete<CustomDelNode>> {}; > + > + // Indirect derivation must still satisfy _Protectable. > + struct FurtherDerived : SimpleNode {}; > + > + // Not hazard-protectable. > + struct PlainClass {}; > +} > + > +// _Protectable concept (libstdc++ internal name for HazardProtectable). > +static_assert(std::__hazard_pointer::_Protectable<SimpleNode>); > +static_assert(std::__hazard_pointer::_Protectable<CustomDelNode>); > +static_assert(std::__hazard_pointer::_Protectable<FurtherDerived>); > + > +static_assert(!std::__hazard_pointer::_Protectable<int>); > +static_assert(!std::__hazard_pointer::_Protectable<void>); > +static_assert(!std::__hazard_pointer::_Protectable<int*>); > + > +static_assert(!std::__hazard_pointer::_Protectable<PlainClass>); > +static_assert(!std::__hazard_pointer::_Protectable<std::string>); > + > +// retire() deleter constraints. > +static_assert(std::is_invocable_v<std::default_delete<SimpleNode>, > SimpleNode*>); > +static_assert(std::is_default_constructible_v<std::default_delete<SimpleNode>>); > +static_assert(std::is_move_assignable_v<std::default_delete<SimpleNode>>); > -- > 2.54.0 >
