diff --git a/README.md b/README.md index 8c23fe2..51ebc21 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ int main() { } ``` +As with `std::shared_ptr`/`std::weak_ptr`, if you need to obtain an observer pointer to an object when you only have `this` (i.e., from a member function), you can inherit from `oup::enable_observer_from_this` to gain access to the `observer_from_this()` member function. This function will return a valid observer pointer as long as the object is owned by a unique or sealed pointer, and will return `nullptr` in all other cases. + ## Limitations @@ -192,4 +194,4 @@ Detail of the benchmarks: ## Alternative implementation -An alternative implementation of an "observable unique pointer" can be found [here](https://www.codeproject.com/articles/1011134/smart-observers-to-use-with-unique-ptr). It does not compile out of the box with gcc unfortunately, but it does contain more features (like creating an observer pointer from a raw `this`) and lacks others (their `make_observable` always performs two allocations). Have a look to check if this better suits your needs. +An alternative implementation of an "observable unique pointer" can be found [here](https://www.codeproject.com/articles/1011134/smart-observers-to-use-with-unique-ptr). It does not compile out of the box with gcc unfortunately and lacks certain features (their `make_observable` always performs two allocations). Have a look to check if this better suits your needs. diff --git a/include/oup/observable_unique_ptr.hpp b/include/oup/observable_unique_ptr.hpp index 02cb067..ca66c17 100644 --- a/include/oup/observable_unique_ptr.hpp +++ b/include/oup/observable_unique_ptr.hpp @@ -11,7 +11,11 @@ namespace oup { template class observer_ptr; +template +class enable_observer_from_this; + namespace details { + struct control_block { enum flag_elements { flag_none = 0, @@ -34,6 +38,7 @@ template struct ptr_and_deleter : Deleter { T* data = nullptr; }; + } /// Simple default deleter @@ -75,6 +80,9 @@ struct placement_delete }; namespace details { + +struct enable_observer_from_this_base {}; + template> class observable_unique_ptr_base { protected: @@ -106,6 +114,16 @@ class observable_unique_ptr_base { delete_and_pop_ref_(block, ptr_deleter.data, ptr_deleter); } + /// Fill in the observer pointer for objects inheriting from enable_observer_from_this. + void set_this_observer_() noexcept { + if constexpr (std::is_base_of_v) { + if (ptr_deleter.data) { + ptr_deleter.data->this_observer.set_data_(block, ptr_deleter.data); + ++block->refcount; + } + } + } + /// Private constructor using pre-allocated control block. /** \param ctrl The control block pointer * \param value The pointer to own @@ -292,6 +310,10 @@ class observable_unique_ptr_base { /** \param other The other pointer to swap with */ void swap(observable_unique_ptr_base& other) noexcept { + if (&other == this) { + return; + } + using std::swap; swap(block, other.block); swap(ptr_deleter, other.ptr_deleter); @@ -345,6 +367,7 @@ class observable_unique_ptr_base { return ptr_deleter.data != nullptr; } }; + } /// Unique-ownership smart pointer, can be observed by observer_ptr, ownership can be released. @@ -381,21 +404,6 @@ class observable_unique_ptr : return new control_block_type; } - static void pop_ref_(control_block_type* block) noexcept { - --block->refcount; - if (block->refcount == 0) { - delete block; - } - } - - static void delete_and_pop_ref_(control_block_type* block, T* data, Deleter& deleter) noexcept { - deleter(data); - - block->set_expired(); - - pop_ref_(block); - } - // Friendship is required for conversions. template friend class observer_ptr; @@ -433,7 +441,9 @@ class observable_unique_ptr : * using make_observable_unique() instead of this constructor. */ explicit observable_unique_ptr(T* value) : - base(value != nullptr ? allocate_block_() : nullptr, value) {} + base(value != nullptr ? allocate_block_() : nullptr, value) { + base::set_this_observer_(); + } /// Explicit ownership capture of a raw pointer, with customer deleter. /** \param value The raw pointer to take ownership of @@ -443,7 +453,9 @@ class observable_unique_ptr : * using make_observable_unique() instead of this constructor. */ explicit observable_unique_ptr(T* value, Deleter del) : - base(value != nullptr ? allocate_block_() : nullptr, value, std::move(del)) {} + base(value != nullptr ? allocate_block_() : nullptr, value, std::move(del)) { + base::set_this_observer_(); + } /// Transfer ownership by implicit casting /** \param value The pointer to take ownership from @@ -474,7 +486,9 @@ class observable_unique_ptr : */ template observable_unique_ptr(observable_unique_ptr&& manager, T* value) noexcept : - base(std::move(manager), value) {} + base(std::move(manager), value) { + base::set_this_observer_(); + } /// Transfer ownership by explicit casting /** \param manager The smart pointer to take ownership from @@ -485,7 +499,9 @@ class observable_unique_ptr : */ template observable_unique_ptr(observable_unique_ptr&& manager, T* value, Deleter del) noexcept : - base(std::move(manager), value, del) {} + base(std::move(manager), value, del) { + base::set_this_observer_(); + } /// Transfer ownership by implicit casting /** \param value The pointer to take ownership from @@ -544,8 +560,10 @@ class observable_unique_ptr : // Delete the old pointer // (this follows std::unique_ptr specs) if (old_ptr) { - delete_and_pop_ref_(old_block, old_ptr, base::ptr_deleter); + base::delete_and_pop_ref_(old_block, old_ptr, base::ptr_deleter); } + + base::set_this_observer_(); } /// Releases ownership of the managed object and mark observers as expired. @@ -608,7 +626,9 @@ class observable_sealed_ptr : * \note This is used by make_observable_sealed(). */ observable_sealed_ptr(control_block_type* ctrl, T* value) noexcept : - base(ctrl, value, oup::placement_delete{}) {} + base(ctrl, value, oup::placement_delete{}) { + base::set_this_observer_(); + } // Friendship is required for conversions. template @@ -744,7 +764,7 @@ observable_sealed_ptr make_observable_sealed(Args&& ... args) { // Allocate memory constexpr std::size_t block_size = sizeof(block_type); constexpr std::size_t object_size = sizeof(T); - std::byte* buffer = new std::byte[block_size + object_size]; + std::byte* buffer = reinterpret_cast(operator new(block_size + object_size)); try { // Construct control block and object @@ -756,7 +776,7 @@ observable_sealed_ptr make_observable_sealed(Args&& ... args) { } catch (...) { // Exception thrown during object construction, // clean up memory and let exception propagate - delete[] buffer; + delete buffer; throw; } } @@ -835,6 +855,9 @@ class observer_ptr { // Friendship is required for conversions. template friend class observer_ptr; + // Friendship is required for enable_observer_from_this. + template + friend class details::observable_unique_ptr_base; using control_block = details::control_block; @@ -848,6 +871,15 @@ class observer_ptr { } } + void set_data_(control_block* b, T* d) noexcept { + if (data) { + pop_ref_(); + } + + block = b; + data = d; + } + public: /// Type of the pointed object using element_type = T; @@ -936,12 +968,8 @@ class observer_ptr { */ template>> observer_ptr& operator=(const observable_unique_ptr& owner) noexcept { - if (data) { - pop_ref_(); - } + set_data_(owner.block, owner.ptr_deleter.data); - block = owner.block; - data = owner.ptr_deleter.data; if (block) { ++block->refcount; } @@ -956,12 +984,8 @@ class observer_ptr { */ template>> observer_ptr& operator=(const observable_sealed_ptr& owner) noexcept { - if (data) { - pop_ref_(); - } + set_data_(owner.block, owner.ptr_deleter.data); - block = owner.block; - data = owner.ptr_deleter.data; if (block) { ++block->refcount; } @@ -973,12 +997,12 @@ class observer_ptr { /** \param value The existing weak pointer to copy */ observer_ptr& operator=(const observer_ptr& value) noexcept { - if (data) { - pop_ref_(); + if (&value == this) { + return *this; } - block = value.block; - data = value.data; + set_data_(value.block, value.data); + if (block) { ++block->refcount; } @@ -993,12 +1017,12 @@ class observer_ptr { */ template>> observer_ptr& operator=(const observer_ptr& value) noexcept { - if (data) { - pop_ref_(); + if (&value == this) { + return *this; } - block = value.block; - data = value.data; + set_data_(value.block, value.data); + if (block) { ++block->refcount; } @@ -1012,13 +1036,9 @@ class observer_ptr { * pointer is set to null and looses ownership. */ observer_ptr& operator=(observer_ptr&& value) noexcept { - if (data) { - pop_ref_(); - } + set_data_(value.block, value.data); - block = value.block; value.block = nullptr; - data = value.data; value.data = nullptr; return *this; @@ -1033,13 +1053,9 @@ class observer_ptr { */ template>> observer_ptr& operator=(observer_ptr&& value) noexcept { - if (data) { - pop_ref_(); - } + set_data_(value.block, value.data); - block = value.block; value.block = nullptr; - data = value.data; value.data = nullptr; return *this; @@ -1114,6 +1130,10 @@ class observer_ptr { /** \param other The other pointer to swap with */ void swap(observer_ptr& other) noexcept { + if (&other == this) { + return; + } + using std::swap; swap(block, other.block); swap(data, other.data); @@ -1153,6 +1173,64 @@ bool operator!= (const observer_ptr& first, const observer_ptr& second) no return first.get() != second.get(); } +/// Enables creating an observer pointer from 'this'. +/** If an object must be able to create an observer pointer to itself, +* without having direct access to the owner pointer (unique or sealed), +* then the object's class can inherit from enable_observer_from_this. +* This provides the observer_from_this() member function, which returns +* a new observer pointer to the object. For this mechanism to work, +* the class must inherit publicly from enable_observer_from_this, +* and the object must be owned by a unique or sealed pointer when +* calling observer_from_this(). If the latter condition is not satisfied, +* i.e., the object was allocated on the stack, or is owned by another +* type of smart pointer, then observer_from_this() will return nullptr. +*/ +template +class enable_observer_from_this : public details::enable_observer_from_this_base { + mutable observer_ptr this_observer; + + // Friendship is required for assignment of the observer. + template + friend class details::observable_unique_ptr_base; + +protected: + enable_observer_from_this() noexcept = default; + + enable_observer_from_this(const enable_observer_from_this&) noexcept { + // Do not copy the other object's observer, this would be an + // invalid reference. + }; + + enable_observer_from_this(enable_observer_from_this&&) noexcept { + // Do not move the other object's observer, this would be an + // invalid reference. + }; + + ~enable_observer_from_this() noexcept = default; + +public: + + /// Return an observer pointer to 'this'. + /** \return A new observer pointer pointing to 'this'. + * \note If 'this' is not owned by a unique or sealed pointer, i.e., if + * the object was allocated on the stack, or if it is owned by another + * type of smart pointer, then this function will return nullptr. + */ + observer_ptr observer_from_this() { + return this_observer; + } + + /// Return a const observer pointer to 'this'. + /** \return A new observer pointer pointing to 'this'. + * \note If 'this' is not owned by a unique or sealed pointer, i.e., if + * the object was allocated on the stack, or if it is owned by another + * type of smart pointer, then this function will return nullptr. + */ + observer_ptr observer_from_this() const { + return this_observer; + } +}; + } #endif diff --git a/tests/runtime_tests.cpp b/tests/runtime_tests.cpp index b1304b6..fd29e73 100644 --- a/tests/runtime_tests.cpp +++ b/tests/runtime_tests.cpp @@ -748,6 +748,102 @@ TEST_CASE("owner move assignment operator valid to valid with deleter", "[owner_ REQUIRE(mem_track.double_del() == 0u); } +TEST_CASE("owner move assignment operator self to self", "[owner_assignment]") { + memory_tracker mem_track; + + { + test_ptr ptr(new test_object); + ptr = std::move(ptr); + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("owner move assignment operator self to self sealed", "[owner_assignment]") { + memory_tracker mem_track; + + { + test_sptr ptr = oup::make_observable_sealed(); + ptr = std::move(ptr); + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("owner move assignment operator self to self with deleter", "[owner_assignment]") { + memory_tracker mem_track; + + { + test_ptr_with_deleter ptr(new test_object, test_deleter{42}); + ptr = std::move(ptr); + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + REQUIRE(instances_deleter == 1); + REQUIRE(ptr.get_deleter().state_ == 0); + } + + REQUIRE(instances == 0); + REQUIRE(instances_deleter == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("owner move assignment operator self to self empty", "[owner_assignment]") { + memory_tracker mem_track; + + { + test_ptr ptr; + ptr = std::move(ptr); + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("owner move assignment operator self to self empty sealed", "[owner_assignment]") { + memory_tracker mem_track; + + { + test_sptr ptr; + ptr = std::move(ptr); + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("owner move assignment operator self to self empty with deleter", "[owner_assignment]") { + memory_tracker mem_track; + + { + test_ptr_with_deleter ptr; + ptr = std::move(ptr); + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + REQUIRE(instances_deleter == 1); + REQUIRE(ptr.get_deleter().state_ == 0); + } + + REQUIRE(instances == 0); + REQUIRE(instances_deleter == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + TEST_CASE("owner comparison valid ptr vs nullptr", "[owner_comparison]") { memory_tracker mem_track; @@ -1291,6 +1387,52 @@ TEST_CASE("owner swap two instances with deleter", "[owner_utility]") { REQUIRE(mem_track.double_del() == 0u); } +TEST_CASE("owner swap self", "[owner_utility]") { + memory_tracker mem_track; + + { + test_ptr ptr(new test_object); + ptr.swap(ptr); + REQUIRE(instances == 1); + REQUIRE(ptr.get() != nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("owner swap self sealed", "[owner_utility]") { + memory_tracker mem_track; + + { + test_sptr ptr = oup::make_observable_sealed(); + ptr.swap(ptr); + REQUIRE(instances == 1); + REQUIRE(ptr.get() != nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("owner swap self with deleter", "[owner_utility]") { + memory_tracker mem_track; + + { + test_ptr_with_deleter ptr(new test_object, test_deleter{43}); + ptr.swap(ptr); + REQUIRE(instances == 1); + REQUIRE(ptr.get() != nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(instances_deleter == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + TEST_CASE("owner dereference", "[owner_utility]") { memory_tracker mem_track; @@ -1986,6 +2128,23 @@ TEST_CASE("observer swap two different instances", "[observer_utility]") { REQUIRE(mem_track.double_del() == 0u); } +TEST_CASE("observer swap self", "[observer_utility]") { + memory_tracker mem_track; + + { + test_ptr ptr_owner(new test_object); + test_optr ptr(ptr_owner); + ptr.swap(ptr); + REQUIRE(instances == 1); + REQUIRE(ptr.get() == ptr_owner.get()); + REQUIRE(ptr.expired() == false); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + TEST_CASE("observer dereference", "[observer_utility]") { memory_tracker mem_track; @@ -2118,6 +2277,59 @@ TEST_CASE("observer copy assignment operator empty to empty", "[observer_assignm REQUIRE(mem_track.double_del() == 0u); } +TEST_CASE("observer copy assignment operator self to self", "[observer_assignment]") { + memory_tracker mem_track; + + { + test_ptr ptr_owner{new test_object}; + test_optr ptr{ptr_owner}; + ptr = ptr; + REQUIRE(instances == 1); + REQUIRE(ptr.get() != nullptr); + REQUIRE(ptr.expired() == false); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer copy assignment operator self to self expired", "[observer_assignment]") { + memory_tracker mem_track; + + { + test_optr ptr; + { + test_ptr ptr_owner{new test_object}; + ptr = ptr_owner; + } + ptr = ptr; + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer copy assignment operator self to self empty", "[observer_assignment]") { + memory_tracker mem_track; + + { + test_optr ptr; + ptr = ptr; + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + TEST_CASE("observer move assignment operator valid to empty", "[observer_assignment]") { memory_tracker mem_track; @@ -2190,6 +2402,59 @@ TEST_CASE("observer move assignment operator empty to empty", "[observer_assignm REQUIRE(mem_track.double_del() == 0u); } +TEST_CASE("observer move assignment operator self to self", "[observer_assignment]") { + memory_tracker mem_track; + + { + test_ptr ptr_owner{new test_object}; + test_optr ptr{ptr_owner}; + ptr = std::move(ptr); + REQUIRE(instances == 1); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer move assignment operator self to self expired", "[observer_assignment]") { + memory_tracker mem_track; + + { + test_optr ptr; + { + test_ptr ptr_owner{new test_object}; + ptr = ptr_owner; + } + ptr = std::move(ptr); + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer move assignment operator self to self empty", "[observer_assignment]") { + memory_tracker mem_track; + + { + test_optr ptr; + ptr = std::move(ptr); + REQUIRE(instances == 0); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + TEST_CASE("observer acquiring assignment operator valid to empty", "[observer_assignment]") { memory_tracker mem_track; @@ -2651,3 +2916,236 @@ TEST_CASE("pointers in vector", "[system_tests]") { REQUIRE(mem_track.leaks() == 0u); REQUIRE(mem_track.double_del() == 0u); } + +TEST_CASE("observer from this", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_ptr_from_this ptr{new test_object_observer_from_this}; + const test_ptr_from_this& cptr = ptr; + + test_optr_from_this optr_from_this = ptr->observer_from_this(); + test_optr_from_this_const optr_from_this_const = cptr->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this_const.expired() == false); + REQUIRE(optr_from_this.get() == ptr.get()); + REQUIRE(optr_from_this_const.get() == ptr.get()); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this sealed", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_sptr_from_this ptr = oup::make_observable_sealed(); + const test_sptr_from_this& cptr = ptr; + + test_optr_from_this optr_from_this = ptr->observer_from_this(); + test_optr_from_this_const optr_from_this_const = cptr->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this_const.expired() == false); + REQUIRE(optr_from_this.get() == ptr.get()); + REQUIRE(optr_from_this_const.get() == ptr.get()); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this derived", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_ptr_from_this_derived ptr{new test_object_observer_from_this_derived}; + const test_ptr_from_this_derived& cptr = ptr; + + test_optr_from_this optr_from_this = ptr->observer_from_this(); + test_optr_from_this_const optr_from_this_const = cptr->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this_const.expired() == false); + REQUIRE(optr_from_this.get() == ptr.get()); + REQUIRE(optr_from_this_const.get() == ptr.get()); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this after move", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_ptr_from_this ptr1{new test_object_observer_from_this}; + test_ptr_from_this ptr2{std::move(ptr1)}; + const test_ptr_from_this& cptr2 = ptr2; + + test_optr_from_this optr_from_this = ptr2->observer_from_this(); + test_optr_from_this_const optr_from_this_const = cptr2->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this_const.expired() == false); + REQUIRE(optr_from_this.get() == ptr2.get()); + REQUIRE(optr_from_this_const.get() == ptr2.get()); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this after move sealed", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_sptr_from_this ptr1 = oup::make_observable_sealed(); + test_sptr_from_this ptr2{std::move(ptr1)}; + const test_sptr_from_this& cptr2 = ptr2; + + test_optr_from_this optr_from_this = ptr2->observer_from_this(); + test_optr_from_this_const optr_from_this_const = cptr2->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this_const.expired() == false); + REQUIRE(optr_from_this.get() == ptr2.get()); + REQUIRE(optr_from_this_const.get() == ptr2.get()); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this after move assignment", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_ptr_from_this ptr1{new test_object_observer_from_this}; + test_ptr_from_this ptr2; + ptr2 = std::move(ptr1); + + const test_ptr_from_this& cptr2 = ptr2; + test_optr_from_this optr_from_this = ptr2->observer_from_this(); + test_optr_from_this_const optr_from_this_const = cptr2->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this_const.expired() == false); + REQUIRE(optr_from_this.get() == ptr2.get()); + REQUIRE(optr_from_this_const.get() == ptr2.get()); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this after move assignment sealed", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_sptr_from_this ptr1 = oup::make_observable_sealed(); + test_sptr_from_this ptr2; + ptr2 = std::move(ptr1); + const test_sptr_from_this& cptr2 = ptr2; + + test_optr_from_this optr_from_this = ptr2->observer_from_this(); + test_optr_from_this_const optr_from_this_const = cptr2->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this_const.expired() == false); + REQUIRE(optr_from_this.get() == ptr2.get()); + REQUIRE(optr_from_this_const.get() == ptr2.get()); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this after release", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_ptr_from_this ptr1{new test_object_observer_from_this}; + test_object_observer_from_this* ptr2 = ptr1.release(); + const test_object_observer_from_this* cptr2 = ptr2; + + test_optr_from_this optr_from_this = ptr2->observer_from_this(); + test_optr_from_this_const optr_from_this_const = cptr2->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == true); + REQUIRE(optr_from_this_const.expired() == true); + REQUIRE(optr_from_this.get() == nullptr); + REQUIRE(optr_from_this_const.get() == nullptr); + + delete ptr2; + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this after release and reset", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_ptr_from_this ptr1{new test_object_observer_from_this}; + test_object_observer_from_this* ptr2 = ptr1.release(); + const test_object_observer_from_this* cptr2 = ptr2; + + test_ptr_from_this ptr3; + ptr3.reset(ptr2); + + test_optr_from_this optr_from_this = ptr2->observer_from_this(); + test_optr_from_this_const optr_from_this_const = cptr2->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this_const.expired() == false); + REQUIRE(optr_from_this.get() == ptr2); + REQUIRE(optr_from_this_const.get() == ptr2); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this stack", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_object_observer_from_this obj; + const test_object_observer_from_this& cobj = obj; + + test_optr_from_this optr_from_this = obj.observer_from_this(); + test_optr_from_this_const optr_from_this_const = cobj.observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == true); + REQUIRE(optr_from_this_const.expired() == true); + REQUIRE(optr_from_this.get() == nullptr); + REQUIRE(optr_from_this_const.get() == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} diff --git a/tests/tests_common.hpp b/tests/tests_common.hpp index 8dbb5f2..c8de086 100644 --- a/tests/tests_common.hpp +++ b/tests/tests_common.hpp @@ -38,6 +38,13 @@ struct test_object_thrower { test_object_thrower& operator=(test_object_thrower&&) = delete; }; +struct test_object_observer_from_this : + public test_object, + public oup::enable_observer_from_this {}; + +struct test_object_observer_from_this_derived : + public test_object_observer_from_this {}; + struct test_deleter { int state_ = 0; @@ -73,6 +80,14 @@ using test_ptr_derived_with_deleter = oup::observable_unique_ptr; using test_sptr_thrower = oup::observable_sealed_ptr; using test_ptr_thrower_with_deleter = oup::observable_unique_ptr; +using test_ptr_from_this = oup::observable_unique_ptr; +using test_sptr_from_this = oup::observable_sealed_ptr; +using test_ptr_from_this_derived = oup::observable_unique_ptr; +using test_sptr_from_this_derived = oup::observable_sealed_ptr; using test_optr = oup::observer_ptr; using test_optr_derived = oup::observer_ptr; +using test_optr_from_this = oup::observer_ptr; +using test_optr_from_this_const = oup::observer_ptr; +using test_optr_from_this_derived = oup::observer_ptr; +using test_optr_from_this_derived_const = oup::observer_ptr;