Require explicit copies for Ref/WeakRef

This commit is contained in:
2025-09-12 11:59:56 -04:00
parent 674ff581e7
commit f89868058a
4 changed files with 145 additions and 162 deletions

View File

@@ -27,6 +27,18 @@ template <typename T> struct PointerTraits<std::shared_ptr<T>> {
return std::make_shared<T>(std::forward<Args>(args)...); return std::make_shared<T>(std::forward<Args>(args)...);
} }
static pointer_type copy(const pointer_type &ptr) {
return ptr; // std::shared_ptr copies implicitly
}
static weak_type as_weak(const pointer_type &ptr) {
return ptr; // std::weak_ptr converts implicitly from std::shared_ptr
}
static weak_type copy_weak(const weak_type &weak) {
return weak; // std::weak_ptr copies implicitly
}
static const char *name() { return "std::shared_ptr"; } static const char *name() { return "std::shared_ptr"; }
static const char *weak_name() { return "std::weak_ptr"; } static const char *weak_name() { return "std::weak_ptr"; }
}; };
@@ -39,6 +51,18 @@ template <typename T> struct PointerTraits<Ref<T>> {
return make_ref<T>(std::forward<Args>(args)...); return make_ref<T>(std::forward<Args>(args)...);
} }
static pointer_type copy(const pointer_type &ptr) {
return ptr.copy(); // Ref requires explicit copy
}
static weak_type as_weak(const pointer_type &ptr) {
return ptr.as_weak(); // Ref requires explicit as_weak
}
static weak_type copy_weak(const weak_type &weak) {
return weak.copy(); // WeakRef requires explicit copy
}
static const char *name() { return "Ref"; } static const char *name() { return "Ref"; }
static const char *weak_name() { return "WeakRef"; } static const char *weak_name() { return "WeakRef"; }
}; };
@@ -67,7 +91,7 @@ void benchmark_copy(ankerl::nanobench::Bench &bench) {
auto original = Traits::make(TestObject{123}); auto original = Traits::make(TestObject{123});
bench.run(std::string(Traits::name()) + " copy", [&] { bench.run(std::string(Traits::name()) + " copy", [&] {
auto copy = original; auto copy = Traits::copy(original);
ankerl::nanobench::doNotOptimizeAway(copy); ankerl::nanobench::doNotOptimizeAway(copy);
}); });
} }
@@ -91,9 +115,9 @@ void benchmark_weak_copy(ankerl::nanobench::Bench &bench) {
force_multithreaded(); force_multithreaded();
auto strong_ptr = Traits::make(TestObject{123}); auto strong_ptr = Traits::make(TestObject{123});
typename Traits::weak_type weak_original = strong_ptr; typename Traits::weak_type weak_original = Traits::as_weak(strong_ptr);
bench.run(std::string(Traits::weak_name()) + " copy", [&] { bench.run(std::string(Traits::weak_name()) + " copy", [&] {
auto weak_copy = weak_original; auto weak_copy = Traits::copy_weak(weak_original);
ankerl::nanobench::doNotOptimizeAway(weak_copy); ankerl::nanobench::doNotOptimizeAway(weak_copy);
}); });
} }
@@ -103,7 +127,7 @@ void benchmark_weak_move(ankerl::nanobench::Bench &bench) {
using Traits = PointerTraits<PtrType>; using Traits = PointerTraits<PtrType>;
auto strong_ptr = Traits::make(TestObject{123}); auto strong_ptr = Traits::make(TestObject{123});
typename Traits::weak_type weak_original = strong_ptr; typename Traits::weak_type weak_original = Traits::as_weak(strong_ptr);
bench.run(std::string(Traits::weak_name()) + " move", [&] { bench.run(std::string(Traits::weak_name()) + " move", [&] {
auto weak_moved = std::move(weak_original); auto weak_moved = std::move(weak_original);
ankerl::nanobench::doNotOptimizeAway(weak_moved); ankerl::nanobench::doNotOptimizeAway(weak_moved);
@@ -126,7 +150,7 @@ void benchmark_weak_lock_success(ankerl::nanobench::Bench &bench) {
using Traits = PointerTraits<PtrType>; using Traits = PointerTraits<PtrType>;
auto strong_ptr = Traits::make(TestObject{789}); auto strong_ptr = Traits::make(TestObject{789});
typename Traits::weak_type weak_ptr = strong_ptr; typename Traits::weak_type weak_ptr = Traits::as_weak(strong_ptr);
bench.run(std::string(Traits::weak_name()) + " lock success", [&] { bench.run(std::string(Traits::weak_name()) + " lock success", [&] {
auto locked = weak_ptr.lock(); auto locked = weak_ptr.lock();
ankerl::nanobench::doNotOptimizeAway(locked); ankerl::nanobench::doNotOptimizeAway(locked);
@@ -140,7 +164,7 @@ void benchmark_weak_lock_failure(ankerl::nanobench::Bench &bench) {
typename Traits::weak_type weak_ptr; typename Traits::weak_type weak_ptr;
{ {
auto strong_ptr = Traits::make(TestObject{999}); auto strong_ptr = Traits::make(TestObject{999});
weak_ptr = strong_ptr; weak_ptr = Traits::as_weak(strong_ptr);
} }
bench.run(std::string(Traits::weak_name()) + " lock failure", [&] { bench.run(std::string(Traits::weak_name()) + " lock failure", [&] {
auto locked = weak_ptr.lock(); auto locked = weak_ptr.lock();
@@ -163,7 +187,7 @@ void benchmark_multithreaded_copy(ankerl::nanobench::Bench &bench,
for (int i = 0; i < num_threads - 1; ++i) { for (int i = 0; i < num_threads - 1; ++i) {
background_threads.emplace_back([&]() { background_threads.emplace_back([&]() {
while (keep_running.load(std::memory_order_relaxed)) { while (keep_running.load(std::memory_order_relaxed)) {
auto copy = ptr; auto copy = Traits::copy(ptr);
ankerl::nanobench::doNotOptimizeAway(copy); ankerl::nanobench::doNotOptimizeAway(copy);
} }
}); });
@@ -171,7 +195,7 @@ void benchmark_multithreaded_copy(ankerl::nanobench::Bench &bench,
// Benchmark the foreground thread under contention // Benchmark the foreground thread under contention
bench.run(std::string(Traits::name()) + " copy under contention", [&] { bench.run(std::string(Traits::name()) + " copy under contention", [&] {
auto copy = ptr; auto copy = Traits::copy(ptr);
ankerl::nanobench::doNotOptimizeAway(copy); ankerl::nanobench::doNotOptimizeAway(copy);
}); });
@@ -189,7 +213,7 @@ void benchmark_multithreaded_weak_lock(ankerl::nanobench::Bench &bench,
// Create the shared object and weak reference outside the benchmark // Create the shared object and weak reference outside the benchmark
auto strong_ptr = Traits::make(TestObject{789}); auto strong_ptr = Traits::make(TestObject{789});
typename Traits::weak_type weak_ptr = strong_ptr; typename Traits::weak_type weak_ptr = Traits::as_weak(strong_ptr);
// Create background threads that will create contention // Create background threads that will create contention
std::atomic<bool> keep_running{true}; std::atomic<bool> keep_running{true};
@@ -224,7 +248,7 @@ void benchmark_weak_copy_with_strong_contention(ankerl::nanobench::Bench &bench,
// Create the shared object and weak reference outside the benchmark // Create the shared object and weak reference outside the benchmark
auto strong_ptr = Traits::make(TestObject{456}); auto strong_ptr = Traits::make(TestObject{456});
typename Traits::weak_type weak_ptr = strong_ptr; typename Traits::weak_type weak_ptr = Traits::as_weak(strong_ptr);
// Create background threads copying the strong pointer // Create background threads copying the strong pointer
std::atomic<bool> keep_running{true}; std::atomic<bool> keep_running{true};
@@ -233,7 +257,7 @@ void benchmark_weak_copy_with_strong_contention(ankerl::nanobench::Bench &bench,
for (int i = 0; i < num_threads - 1; ++i) { for (int i = 0; i < num_threads - 1; ++i) {
background_threads.emplace_back([&]() { background_threads.emplace_back([&]() {
while (keep_running.load(std::memory_order_relaxed)) { while (keep_running.load(std::memory_order_relaxed)) {
auto copy = strong_ptr; auto copy = Traits::copy(strong_ptr);
ankerl::nanobench::doNotOptimizeAway(copy); ankerl::nanobench::doNotOptimizeAway(copy);
} }
}); });
@@ -242,7 +266,7 @@ void benchmark_weak_copy_with_strong_contention(ankerl::nanobench::Bench &bench,
// Benchmark weak reference copying under strong reference contention // Benchmark weak reference copying under strong reference contention
bench.run(std::string(Traits::weak_name()) + " copy with strong contention", bench.run(std::string(Traits::weak_name()) + " copy with strong contention",
[&] { [&] {
auto weak_copy = weak_ptr; auto weak_copy = Traits::copy_weak(weak_ptr);
ankerl::nanobench::doNotOptimizeAway(weak_copy); ankerl::nanobench::doNotOptimizeAway(weak_copy);
}); });
@@ -260,7 +284,7 @@ void benchmark_strong_copy_with_weak_contention(ankerl::nanobench::Bench &bench,
// Create the shared object and weak reference outside the benchmark // Create the shared object and weak reference outside the benchmark
auto strong_ptr = Traits::make(TestObject{789}); auto strong_ptr = Traits::make(TestObject{789});
typename Traits::weak_type weak_ptr = strong_ptr; typename Traits::weak_type weak_ptr = Traits::as_weak(strong_ptr);
// Create background threads copying the weak pointer // Create background threads copying the weak pointer
std::atomic<bool> keep_running{true}; std::atomic<bool> keep_running{true};
@@ -269,7 +293,7 @@ void benchmark_strong_copy_with_weak_contention(ankerl::nanobench::Bench &bench,
for (int i = 0; i < num_threads - 1; ++i) { for (int i = 0; i < num_threads - 1; ++i) {
background_threads.emplace_back([&]() { background_threads.emplace_back([&]() {
while (keep_running.load(std::memory_order_relaxed)) { while (keep_running.load(std::memory_order_relaxed)) {
auto weak_copy = weak_ptr; auto weak_copy = Traits::copy_weak(weak_ptr);
ankerl::nanobench::doNotOptimizeAway(weak_copy); ankerl::nanobench::doNotOptimizeAway(weak_copy);
} }
}); });
@@ -277,7 +301,7 @@ void benchmark_strong_copy_with_weak_contention(ankerl::nanobench::Bench &bench,
// Benchmark strong reference copying under weak reference contention // Benchmark strong reference copying under weak reference contention
bench.run(std::string(Traits::name()) + " copy with weak contention", [&] { bench.run(std::string(Traits::name()) + " copy with weak contention", [&] {
auto strong_copy = strong_ptr; auto strong_copy = Traits::copy(strong_ptr);
ankerl::nanobench::doNotOptimizeAway(strong_copy); ankerl::nanobench::doNotOptimizeAway(strong_copy);
}); });

View File

@@ -22,8 +22,8 @@
* Basic usage: * Basic usage:
* @code * @code
* auto obj = make_ref<MyClass>(args...); // Create managed object * auto obj = make_ref<MyClass>(args...); // Create managed object
* auto copy = obj; // Copy (thread-safe) * auto copy = obj.copy(); // Explicit copy (thread-safe)
* WeakRef<MyClass> weak = obj; // Create weak reference * WeakRef<MyClass> weak = obj.as_weak(); // Create weak reference
* auto locked = weak.lock(); // Try to promote to strong * auto locked = weak.lock(); // Try to promote to strong
* @endcode * @endcode
* *
@@ -31,6 +31,9 @@
* safely copy, move, and destroy references to the same object. * safely copy, move, and destroy references to the same object.
*/ */
// Forward declaration
template <typename T> struct WeakRef;
namespace detail { namespace detail {
struct ControlBlock { struct ControlBlock {
std::atomic<uint32_t> strong_count; std::atomic<uint32_t> strong_count;
@@ -126,57 +129,28 @@ template <typename T> struct Ref {
~Ref() { release(); } ~Ref() { release(); }
/** /**
* @brief Copy constructor - increments strong reference count * @brief Copy constructor - deleted to prevent accidental copies
* Use copy() method for explicit copying
*/ */
Ref(const Ref &other) noexcept Ref(const Ref &other) = delete;
: ptr(other.ptr), control_block(other.control_block) {
if (control_block) {
control_block->increment_strong();
}
}
/** /**
* @brief Converting copy constructor for polymorphism (Derived -> Base) * @brief Converting copy constructor - deleted to prevent accidental copies
* Use copy() method for explicit copying
*/ */
template <typename U> template <typename U> Ref(const Ref<U> &other) = delete;
Ref(const Ref<U> &other) noexcept
requires std::is_convertible_v<U *, T *>
: ptr(other.ptr), control_block(other.control_block) {
if (control_block) {
control_block->increment_strong();
}
}
/** /**
* @brief Copy assignment operator * @brief Copy assignment operator - deleted to prevent accidental copies
* Use copy() method for explicit copying
*/ */
Ref &operator=(const Ref &other) noexcept { Ref &operator=(const Ref &other) = delete;
if (this != &other) {
release();
ptr = other.ptr;
control_block = other.control_block;
if (control_block) {
control_block->increment_strong();
}
}
return *this;
}
/** /**
* @brief Converting assignment operator for polymorphism (Derived -> Base) * @brief Converting assignment operator - deleted to prevent accidental
* copies Use copy() method for explicit copying
*/ */
template <typename U> template <typename U> Ref &operator=(const Ref<U> &other) = delete;
Ref &operator=(const Ref<U> &other) noexcept
requires std::is_convertible_v<U *, T *>
{
release();
ptr = other.ptr;
control_block = other.control_block;
if (control_block) {
control_block->increment_strong();
}
return *this;
}
/** /**
* @brief Move constructor - transfers ownership * @brief Move constructor - transfers ownership
@@ -228,6 +202,28 @@ template <typename T> struct Ref {
return *this; return *this;
} }
/**
* @brief Explicitly create a copy with shared ownership
* @return New Ref that shares ownership of the same object
*/
[[nodiscard]] Ref copy() const noexcept {
if (control_block) {
control_block->increment_strong();
}
return Ref(ptr, control_block);
}
/**
* @brief Create a WeakRef that observes this object
* @return New WeakRef that observes the same object
*/
[[nodiscard]] WeakRef<T> as_weak() const noexcept {
if (control_block) {
control_block->increment_weak();
}
return WeakRef<T>(control_block);
}
/** /**
* @brief Reset to empty state * @brief Reset to empty state
*/ */
@@ -347,76 +343,40 @@ template <typename T> struct WeakRef {
~WeakRef() { release(); } ~WeakRef() { release(); }
/** /**
* @brief Copy constructor from WeakRef * @brief Copy constructor from WeakRef - deleted to prevent accidental copies
* Use copy() method for explicit copying
*/ */
WeakRef(const WeakRef &other) noexcept : control_block(other.control_block) { WeakRef(const WeakRef &other) = delete;
if (control_block) {
control_block->increment_weak();
}
}
/** /**
* @brief Copy constructor from Ref * @brief Copy constructor from Ref - deleted to prevent accidental copies
* Use copy() method for explicit copying
*/ */
WeakRef(const Ref<T> &ref) noexcept : control_block(ref.control_block) { WeakRef(const Ref<T> &ref) = delete;
if (control_block) {
control_block->increment_weak();
}
}
/** /**
* @brief Converting copy constructor from WeakRef for polymorphism * @brief Converting copy constructor from WeakRef - deleted to prevent
* accidental copies Use copy() method for explicit copying
*/ */
template <typename U> template <typename U> WeakRef(const WeakRef<U> &other) = delete;
WeakRef(const WeakRef<U> &other) noexcept
requires std::is_convertible_v<U *, T *>
: control_block(other.control_block) {
if (control_block) {
control_block->increment_weak();
}
}
/** /**
* @brief Converting copy constructor from Ref for polymorphism * @brief Converting copy constructor from Ref - deleted to prevent accidental
* copies Use copy() method for explicit copying
*/ */
template <typename U> template <typename U> WeakRef(const Ref<U> &ref) = delete;
WeakRef(const Ref<U> &ref) noexcept
requires std::is_convertible_v<U *, T *>
: control_block(ref.control_block) {
if (control_block) {
control_block->increment_weak();
}
}
/** /**
* @brief Converting copy assignment from WeakRef for polymorphism * @brief Converting copy assignment from WeakRef - deleted to prevent
* accidental copies Use copy() method for explicit copying
*/ */
template <typename U> template <typename U> WeakRef &operator=(const WeakRef<U> &other) = delete;
WeakRef &operator=(const WeakRef<U> &other) noexcept
requires std::is_convertible_v<U *, T *>
{
release();
control_block = other.control_block;
if (control_block) {
control_block->increment_weak();
}
return *this;
}
/** /**
* @brief Converting copy assignment from Ref for polymorphism * @brief Converting copy assignment from Ref - deleted to prevent accidental
* copies Use copy() method for explicit copying
*/ */
template <typename U> template <typename U> WeakRef &operator=(const Ref<U> &ref) = delete;
WeakRef &operator=(const Ref<U> &ref) noexcept
requires std::is_convertible_v<U *, T *>
{
release();
control_block = ref.control_block;
if (control_block) {
control_block->increment_weak();
}
return *this;
}
/** /**
* @brief Converting move constructor from WeakRef for polymorphism * @brief Converting move constructor from WeakRef for polymorphism
@@ -442,30 +402,16 @@ template <typename T> struct WeakRef {
} }
/** /**
* @brief Copy assignment from WeakRef * @brief Copy assignment from WeakRef - deleted to prevent accidental copies
* Use copy() method for explicit copying
*/ */
WeakRef &operator=(const WeakRef &other) noexcept { WeakRef &operator=(const WeakRef &other) = delete;
if (this != &other) {
release();
control_block = other.control_block;
if (control_block) {
control_block->increment_weak();
}
}
return *this;
}
/** /**
* @brief Copy assignment from Ref * @brief Copy assignment from Ref - deleted to prevent accidental copies
* Use copy() method for explicit copying
*/ */
WeakRef &operator=(const Ref<T> &ref) noexcept { WeakRef &operator=(const Ref<T> &ref) = delete;
release();
control_block = ref.control_block;
if (control_block) {
control_block->increment_weak();
}
return *this;
}
/** /**
* @brief Move constructor * @brief Move constructor
@@ -486,6 +432,17 @@ template <typename T> struct WeakRef {
return *this; return *this;
} }
/**
* @brief Explicitly create a copy with shared weak reference
* @return New WeakRef that observes the same object
*/
[[nodiscard]] WeakRef copy() const noexcept {
if (control_block) {
control_block->increment_weak();
}
return WeakRef(control_block);
}
/** /**
* @brief Reset to empty state * @brief Reset to empty state
*/ */
@@ -550,6 +507,8 @@ private:
* @code * @code
* auto obj = make_ref<MyClass>(arg1, arg2); * auto obj = make_ref<MyClass>(arg1, arg2);
* auto empty_vec = make_ref<std::vector<int>>(); * auto empty_vec = make_ref<std::vector<int>>();
* auto obj_copy = obj.copy(); // Explicit copy
* WeakRef<MyClass> weak = obj.as_weak(); // Create weak reference
* @endcode * @endcode
* *
* Thread safety: Safe to call from multiple threads simultaneously. * Thread safety: Safe to call from multiple threads simultaneously.

View File

@@ -25,7 +25,7 @@ Ref<Server> Server::create(const weaseldb::Config &config,
ConnectionHandler &handler, ConnectionHandler &handler,
const std::vector<int> &listen_fds) { const std::vector<int> &listen_fds) {
auto result = make_ref<Server>(config, handler, listen_fds); auto result = make_ref<Server>(config, handler, listen_fds);
result->self_ = result; result->self_ = result.as_weak();
return result; return result;
} }
@@ -218,7 +218,7 @@ int Server::create_local_connection() {
// Create Connection object // Create Connection object
auto connection = std::unique_ptr<Connection>(new Connection( auto connection = std::unique_ptr<Connection>(new Connection(
addr, server_fd, connection_id_.fetch_add(1, std::memory_order_relaxed), addr, server_fd, connection_id_.fetch_add(1, std::memory_order_relaxed),
epoll_index, &handler_, self_)); epoll_index, &handler_, self_.copy()));
// Store in registry // Store in registry
connection_registry_.store(server_fd, std::move(connection)); connection_registry_.store(server_fd, std::move(connection));
@@ -422,7 +422,7 @@ void Server::start_io_threads(std::vector<std::thread> &threads) {
batch[batch_count] = std::unique_ptr<Connection>(new Connection( batch[batch_count] = std::unique_ptr<Connection>(new Connection(
addr, fd, addr, fd,
connection_id_.fetch_add(1, std::memory_order_relaxed), connection_id_.fetch_add(1, std::memory_order_relaxed),
epoll_index, &handler_, self_)); epoll_index, &handler_, self_.copy()));
batch_events[batch_count] = batch_events[batch_count] =
EPOLLIN; // New connections always start with read EPOLLIN; // New connections always start with read
batch_count++; batch_count++;

View File

@@ -52,9 +52,9 @@ TEST_CASE("Ref basic functionality") {
CHECK((*ref).value == 42); CHECK((*ref).value == 42);
} }
SUBCASE("copy construction increments reference count") { SUBCASE("explicit copy increments reference count") {
auto ref1 = make_ref<TestObject>(123); auto ref1 = make_ref<TestObject>(123);
auto ref2 = ref1; auto ref2 = ref1.copy();
CHECK(ref1); CHECK(ref1);
CHECK(ref2); CHECK(ref2);
@@ -63,11 +63,11 @@ TEST_CASE("Ref basic functionality") {
CHECK(ref2->value == 123); CHECK(ref2->value == 123);
} }
SUBCASE("copy assignment works correctly") { SUBCASE("explicit copy assignment works correctly") {
auto ref1 = make_ref<TestObject>(100); auto ref1 = make_ref<TestObject>(100);
auto ref2 = make_ref<TestObject>(200); auto ref2 = make_ref<TestObject>(200);
ref2 = ref1; ref2 = ref1.copy();
CHECK(ref1.get() == ref2.get()); CHECK(ref1.get() == ref2.get());
CHECK(ref1->value == 100); CHECK(ref1->value == 100);
CHECK(ref2->value == 100); CHECK(ref2->value == 100);
@@ -109,7 +109,7 @@ TEST_CASE("Ref basic functionality") {
TEST_CASE("WeakRef basic functionality") { TEST_CASE("WeakRef basic functionality") {
SUBCASE("construction from Ref") { SUBCASE("construction from Ref") {
auto ref = make_ref<TestObject>(333); auto ref = make_ref<TestObject>(333);
WeakRef<TestObject> weak_ref = ref; WeakRef<TestObject> weak_ref = ref.as_weak();
auto locked = weak_ref.lock(); auto locked = weak_ref.lock();
CHECK(locked); CHECK(locked);
@@ -121,7 +121,7 @@ TEST_CASE("WeakRef basic functionality") {
WeakRef<TestObject> weak_ref; WeakRef<TestObject> weak_ref;
{ {
auto ref = make_ref<TestObject>(444); auto ref = make_ref<TestObject>(444);
weak_ref = ref; weak_ref = ref.as_weak();
} }
// ref goes out of scope, object should be destroyed // ref goes out of scope, object should be destroyed
@@ -131,8 +131,8 @@ TEST_CASE("WeakRef basic functionality") {
SUBCASE("copy and move semantics") { SUBCASE("copy and move semantics") {
auto ref = make_ref<TestObject>(666); auto ref = make_ref<TestObject>(666);
WeakRef<TestObject> weak1 = ref; WeakRef<TestObject> weak1 = ref.as_weak();
WeakRef<TestObject> weak2 = weak1; // copy WeakRef<TestObject> weak2 = weak1.copy(); // explicit copy
WeakRef<TestObject> weak3 = std::move(weak1); // move WeakRef<TestObject> weak3 = std::move(weak1); // move
auto locked2 = weak2.lock(); auto locked2 = weak2.lock();
@@ -160,7 +160,7 @@ TEST_CASE("Ref thread safety") {
start_latch.arrive_and_wait(); start_latch.arrive_and_wait();
for (int j = 0; j < copies_per_thread; ++j) { for (int j = 0; j < copies_per_thread; ++j) {
auto copy = ref; auto copy = ref.copy();
CHECK(copy); CHECK(copy);
CHECK(copy->value == 777); CHECK(copy->value == 777);
} }
@@ -191,7 +191,7 @@ TEST_CASE("Control block cleanup race condition test") {
WeakRef<TestObject> ptr2; WeakRef<TestObject> ptr2;
auto setup = [&]() { auto setup = [&]() {
ptr1 = make_ref<TestObject>(0); ptr1 = make_ref<TestObject>(0);
ptr2 = ptr1; ptr2 = ptr1.as_weak();
}; };
// Barrier for synchronization - 2 participants (main thread + worker thread) // Barrier for synchronization - 2 participants (main thread + worker thread)
@@ -243,7 +243,7 @@ TEST_CASE("WeakRef prevents circular references") {
// Create object and weak reference // Create object and weak reference
{ {
auto ref = make_ref<TestObject>(123); auto ref = make_ref<TestObject>(123);
weak_ref = ref; weak_ref = ref.as_weak();
// Should be able to lock while object exists // Should be able to lock while object exists
auto locked = weak_ref.lock(); auto locked = weak_ref.lock();
@@ -262,8 +262,8 @@ TEST_CASE("WeakRef prevents circular references") {
auto child = make_ref<Node>(2); auto child = make_ref<Node>(2);
// Create potential cycle // Create potential cycle
parent->next = child; // Strong reference: parent → child parent->next = child.copy(); // Strong reference: parent → child
child->parent = parent; // WeakRef: child ⇝ parent (breaks cycle) child->parent = parent.as_weak(); // WeakRef: child ⇝ parent (breaks cycle)
CHECK(parent->data == 1); CHECK(parent->data == 1);
CHECK(child->data == 2); CHECK(child->data == 2);
@@ -286,7 +286,7 @@ TEST_CASE("Polymorphic Ref conversions") {
CHECK(derived_ref->get_value() == 30); // 10 + 20 CHECK(derived_ref->get_value() == 30); // 10 + 20
// Convert Ref<Derived> to Ref<Base> // Convert Ref<Derived> to Ref<Base>
Ref<Base> base_ref = derived_ref; Ref<Base> base_ref = derived_ref.copy();
CHECK(base_ref); CHECK(base_ref);
CHECK(base_ref->get_value() == 30); // Virtual dispatch works CHECK(base_ref->get_value() == 30); // Virtual dispatch works
CHECK(base_ref->base_value == 10); CHECK(base_ref->base_value == 10);
@@ -303,7 +303,7 @@ TEST_CASE("Polymorphic Ref conversions") {
CHECK(base_ref->get_value() == 100); CHECK(base_ref->get_value() == 100);
// Assign derived to base // Assign derived to base
base_ref = derived_ref; base_ref = derived_ref.copy();
CHECK(base_ref->get_value() == 20); // 5 + 15 CHECK(base_ref->get_value() == 20); // 5 + 15
CHECK(base_ref.get() == derived_ref.get()); CHECK(base_ref.get() == derived_ref.get());
} }
@@ -338,7 +338,7 @@ TEST_CASE("Polymorphic Ref conversions") {
CHECK(another_derived->get_value() == 24); // 6 * 4 CHECK(another_derived->get_value() == 24); // 6 * 4
// Convert to base // Convert to base
Ref<Base> base_ref = another_derived; Ref<Base> base_ref = another_derived.copy();
CHECK(base_ref->get_value() == 24); // Virtual dispatch CHECK(base_ref->get_value() == 24); // Virtual dispatch
CHECK(base_ref.get() == another_derived.get()); CHECK(base_ref.get() == another_derived.get());
} }
@@ -349,10 +349,10 @@ TEST_CASE("Polymorphic WeakRef conversions") {
auto derived_ref = make_ref<Derived>(3, 7); auto derived_ref = make_ref<Derived>(3, 7);
// Create WeakRef<Derived> // Create WeakRef<Derived>
WeakRef<Derived> weak_derived = derived_ref; WeakRef<Derived> weak_derived = derived_ref.as_weak();
// Convert to WeakRef<Base> // Convert to WeakRef<Base>
WeakRef<Base> weak_base = weak_derived; WeakRef<Base> weak_base = weak_derived.copy();
// Both should lock to same object // Both should lock to same object
auto locked_derived = weak_derived.lock(); auto locked_derived = weak_derived.lock();
@@ -368,11 +368,11 @@ TEST_CASE("Polymorphic WeakRef conversions") {
auto derived_ref = make_ref<Derived>(4, 6); auto derived_ref = make_ref<Derived>(4, 6);
auto base_ref = make_ref<Base>(999); auto base_ref = make_ref<Base>(999);
WeakRef<Derived> weak_derived = derived_ref; WeakRef<Derived> weak_derived = derived_ref.as_weak();
WeakRef<Base> weak_base = base_ref; WeakRef<Base> weak_base = base_ref.as_weak();
// Assign derived weak ref to base weak ref // Assign derived weak ref to base weak ref
weak_base = weak_derived; weak_base = weak_derived.copy();
auto locked = weak_base.lock(); auto locked = weak_base.lock();
CHECK(locked); CHECK(locked);
@@ -384,7 +384,7 @@ TEST_CASE("Polymorphic WeakRef conversions") {
auto derived_ref = make_ref<Derived>(2, 8); auto derived_ref = make_ref<Derived>(2, 8);
// Create WeakRef<Base> directly from Ref<Derived> // Create WeakRef<Base> directly from Ref<Derived>
WeakRef<Base> weak_base = derived_ref; WeakRef<Base> weak_base = derived_ref.as_weak();
auto locked = weak_base.lock(); auto locked = weak_base.lock();
CHECK(locked); CHECK(locked);
@@ -394,7 +394,7 @@ TEST_CASE("Polymorphic WeakRef conversions") {
SUBCASE("WeakRef move operations") { SUBCASE("WeakRef move operations") {
auto derived_ref = make_ref<Derived>(1, 9); auto derived_ref = make_ref<Derived>(1, 9);
WeakRef<Derived> weak_derived = derived_ref; WeakRef<Derived> weak_derived = derived_ref.as_weak();
// Move construct // Move construct
WeakRef<Base> weak_base = std::move(weak_derived); WeakRef<Base> weak_base = std::move(weak_derived);
@@ -414,7 +414,7 @@ TEST_CASE("Polymorphic edge cases") {
CHECK(!empty_derived); CHECK(!empty_derived);
// Convert empty derived to base // Convert empty derived to base
Ref<Base> empty_base = empty_derived; Ref<Base> empty_base = empty_derived.copy();
CHECK(!empty_base); CHECK(!empty_base);
// Move empty derived to base // Move empty derived to base
@@ -427,7 +427,7 @@ TEST_CASE("Polymorphic edge cases") {
CHECK(!empty_weak_derived.lock()); CHECK(!empty_weak_derived.lock());
// Convert empty weak derived to weak base // Convert empty weak derived to weak base
WeakRef<Base> empty_weak_base = empty_weak_derived; WeakRef<Base> empty_weak_base = empty_weak_derived.copy();
CHECK(!empty_weak_base.lock()); CHECK(!empty_weak_base.lock());
} }
@@ -435,7 +435,7 @@ TEST_CASE("Polymorphic edge cases") {
auto derived_ref = make_ref<Derived>(5, 5); auto derived_ref = make_ref<Derived>(5, 5);
// Ref<Derived> → WeakRef<Base> // Ref<Derived> → WeakRef<Base>
WeakRef<Base> weak_base_from_ref = derived_ref; WeakRef<Base> weak_base_from_ref = derived_ref.as_weak();
// WeakRef<Base> → Ref<Base> via lock // WeakRef<Base> → Ref<Base> via lock
auto base_ref_from_weak = weak_base_from_ref.lock(); auto base_ref_from_weak = weak_base_from_ref.lock();
@@ -457,5 +457,5 @@ TEST_CASE("Self-referencing WeakRef pattern") {
WeakRef<SelfReferencing> self_; WeakRef<SelfReferencing> self_;
}; };
auto x = make_ref<SelfReferencing>(); auto x = make_ref<SelfReferencing>();
x->self_ = x; x->self_ = x.as_weak();
} }