From b44f93d5265e88ffe9b8080ad3ce1f391a5bc1dc Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 11:23:00 +0000 Subject: [PATCH 01/12] Ensure we set the "observer pointer from this" in all possible scenarios --- include/oup/observable_unique_ptr.hpp | 67 +++++++++++++++++-------- tests/runtime_tests.cpp | 72 +++++++++++++++++++++++++++ tests/tests_common.hpp | 2 + 3 files changed, 121 insertions(+), 20 deletions(-) diff --git a/include/oup/observable_unique_ptr.hpp b/include/oup/observable_unique_ptr.hpp index ca66c17..14cb1c3 100644 --- a/include/oup/observable_unique_ptr.hpp +++ b/include/oup/observable_unique_ptr.hpp @@ -115,10 +115,20 @@ class observable_unique_ptr_base { } /// 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); + /** \note It is important to preserve the type of the pointer as supplied by the user. + * It might be of a derived type that inherits from enable_observer_from_this, while + * the base type T might not. We still want to fill in the observer pointer if we can. + */ + template + void set_this_observer_(U* p) noexcept { + if constexpr (std::is_convertible_v) { + using input_observer_type = std::decay_tthis_observer)>; + using observer_element_type = typename input_observer_type::element_type; + static_assert(std::is_convertible_v, + "incompatible type specified in enable_observer_from_this"); + + if (p) { + p->this_observer.set_data_(block, p); ++block->refcount; } } @@ -440,9 +450,10 @@ class observable_unique_ptr : * observable_unique_ptr is created. If possible, prefer * using make_observable_unique() instead of this constructor. */ - explicit observable_unique_ptr(T* value) : + template>> + explicit observable_unique_ptr(U* value) : base(value != nullptr ? allocate_block_() : nullptr, value) { - base::set_this_observer_(); + base::set_this_observer_(value); } /// Explicit ownership capture of a raw pointer, with customer deleter. @@ -452,9 +463,10 @@ class observable_unique_ptr : * observable_unique_ptr is created. If possible, prefer * using make_observable_unique() instead of this constructor. */ - explicit observable_unique_ptr(T* value, Deleter del) : + template>> + explicit observable_unique_ptr(U* value, Deleter del) : base(value != nullptr ? allocate_block_() : nullptr, value, std::move(del)) { - base::set_this_observer_(); + base::set_this_observer_(value); } /// Transfer ownership by implicit casting @@ -484,10 +496,11 @@ class observable_unique_ptr : * pointer is set to null and looses ownership. The deleter * is default constructed. */ - template - observable_unique_ptr(observable_unique_ptr&& manager, T* value) noexcept : + template>> + observable_unique_ptr(observable_unique_ptr&& manager, V* value) noexcept : base(std::move(manager), value) { - base::set_this_observer_(); + base::set_this_observer_(value); } /// Transfer ownership by explicit casting @@ -497,10 +510,11 @@ class observable_unique_ptr : * \note After this observable_unique_ptr is created, the source * pointer is set to null and looses ownership. */ - template - observable_unique_ptr(observable_unique_ptr&& manager, T* value, Deleter del) noexcept : + template>> + observable_unique_ptr(observable_unique_ptr&& manager, V* value, Deleter del) noexcept : base(std::move(manager), value, del) { - base::set_this_observer_(); + base::set_this_observer_(value); } /// Transfer ownership by implicit casting @@ -548,7 +562,8 @@ class observable_unique_ptr : /// Replaces the managed object. /** \param ptr A nullptr_t instance */ - void reset(T* ptr) noexcept { + template>> + void reset(U* ptr) noexcept { // Copy old pointer T* old_ptr = base::ptr_deleter.data; control_block_type* old_block = base::block; @@ -563,7 +578,7 @@ class observable_unique_ptr : base::delete_and_pop_ref_(old_block, old_ptr, base::ptr_deleter); } - base::set_this_observer_(); + base::set_this_observer_(ptr); } /// Releases ownership of the managed object and mark observers as expired. @@ -625,9 +640,10 @@ class observable_sealed_ptr : * \param value The pointer to own * \note This is used by make_observable_sealed(). */ - observable_sealed_ptr(control_block_type* ctrl, T* value) noexcept : + template>> + observable_sealed_ptr(control_block_type* ctrl, U* value) noexcept : base(ctrl, value, oup::placement_delete{}) { - base::set_this_observer_(); + base::set_this_observer_(value); } // Friendship is required for conversions. @@ -659,7 +675,8 @@ class observable_sealed_ptr : /// Explicit ownership capture of a raw pointer is forbidden. /** \note If you need to do this, use observable_unique_ptr instead. */ - explicit observable_sealed_ptr(T*) = delete; + template + explicit observable_sealed_ptr(U*) = delete; /// Transfer ownership by implicit casting /** \param value The pointer to take ownership from @@ -760,6 +777,7 @@ observable_unique_ptr make_observable_unique(Args&& ... args) { template observable_sealed_ptr make_observable_sealed(Args&& ... args) { using block_type = typename observable_sealed_ptr::control_block_type; + using decayed_type = std::decay_t; // Allocate memory constexpr std::size_t block_size = sizeof(block_type); @@ -769,7 +787,7 @@ observable_sealed_ptr make_observable_sealed(Args&& ... args) { try { // Construct control block and object block_type* block = new (buffer) block_type; - T* ptr = new (buffer + block_size) T(std::forward(args)...); + decayed_type* ptr = new (buffer + block_size) decayed_type(std::forward(args)...); // Make owner pointer return observable_sealed_ptr(block, ptr); @@ -1184,6 +1202,15 @@ bool operator!= (const observer_ptr& first, const observer_ptr& second) no * 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. +* +* **Corner cases.** +* - If a class A inherits from both another class B and enable_observer_from_this, +* and it is owned by an observable_unique_ptr. The function observer_from_this() +* will always return nullptr if ownership is taken from a pointer to B*, but will +* be a valid pointer if ownership is taken from a pointer to A*. +* +* observable_unique_ptr p(new A); // observer pointer is valid +* observable_unique_ptr p(static_cast(new A)); // observer pointer is nullptr */ template class enable_observer_from_this : public details::enable_observer_from_this_base { diff --git a/tests/runtime_tests.cpp b/tests/runtime_tests.cpp index fd29e73..c895714 100644 --- a/tests/runtime_tests.cpp +++ b/tests/runtime_tests.cpp @@ -2983,6 +2983,78 @@ TEST_CASE("observer from this derived", "[observer_from_this]") { REQUIRE(mem_track.double_del() == 0u); } +TEST_CASE("observer from this derived into base", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_object_observer_from_this* orig_ptr = new test_object_observer_from_this; + test_ptr ptr{orig_ptr}; + + test_optr_from_this optr_from_this = orig_ptr->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this.get() == ptr.get()); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this derived into base after cast", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_object_observer_from_this* orig_ptr = new test_object_observer_from_this; + test_ptr ptr{static_cast(orig_ptr)}; + + test_optr_from_this optr_from_this = orig_ptr->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == true); + REQUIRE(optr_from_this.get() == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this const", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_cptr_from_this ptr{new test_object_observer_from_this}; + test_optr_from_this_const optr_from_this = ptr->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this.get() == ptr.get()); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer from this const sealed", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_csptr_from_this ptr = oup::make_observable_sealed(); + test_optr_from_this_const optr_from_this = ptr->observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this.expired() == false); + REQUIRE(optr_from_this.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; diff --git a/tests/tests_common.hpp b/tests/tests_common.hpp index c8de086..bf11de1 100644 --- a/tests/tests_common.hpp +++ b/tests/tests_common.hpp @@ -82,6 +82,8 @@ 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_cptr_from_this = oup::observable_unique_ptr; +using test_csptr_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; From acf661ced133f36bd8c0565c66a5b82a147fe8cd Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 11:23:56 +0000 Subject: [PATCH 02/12] Added enable_observer_from_this test with object on heap --- tests/runtime_tests.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/runtime_tests.cpp b/tests/runtime_tests.cpp index c895714..dba77df 100644 --- a/tests/runtime_tests.cpp +++ b/tests/runtime_tests.cpp @@ -3221,3 +3221,27 @@ TEST_CASE("observer from this stack", "[observer_from_this]") { REQUIRE(mem_track.leaks() == 0u); REQUIRE(mem_track.double_del() == 0u); } + +TEST_CASE("observer from this heap", "[observer_from_this]") { + memory_tracker mem_track; + + { + test_object_observer_from_this* obj = new test_object_observer_from_this; + 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); + + delete obj; + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} From 7ca887d43b6bd62cb7b87f15524fc62655b1620d Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 12:34:51 +0000 Subject: [PATCH 03/12] Added support for multiple inheritance with enable_observable_from_this --- README.md | 2 +- include/oup/observable_unique_ptr.hpp | 124 +++++++++++++++++++++----- tests/runtime_tests.cpp | 26 ++++++ tests/tests_common.hpp | 8 ++ 4 files changed, 138 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 51ebc21..fee25bb 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ 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. +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. Contrary to `std::enable_shared_from_this`, this feature naturally supports multiple inheritance. ## Limitations diff --git a/include/oup/observable_unique_ptr.hpp b/include/oup/observable_unique_ptr.hpp index 14cb1c3..45a07d1 100644 --- a/include/oup/observable_unique_ptr.hpp +++ b/include/oup/observable_unique_ptr.hpp @@ -79,9 +79,38 @@ struct placement_delete } }; +template +class enable_observer_from_this; + namespace details { -struct enable_observer_from_this_base {}; +struct enable_observer_from_this_base { + using control_block = details::control_block; + + mutable control_block* this_control_block = nullptr; + + void pop_ref_() noexcept { + --this_control_block->refcount; + if (this_control_block->refcount == 0) { + delete this_control_block; + } + } + + void set_control_block_(control_block* b) noexcept { + if (this_control_block) { + pop_ref_(); + } + + this_control_block = b; + if (this_control_block) { + ++this_control_block->refcount; + } + } + + // Friendship is required for assignment of the observer. + template + friend class observable_unique_ptr_base; +}; template> class observable_unique_ptr_base { @@ -122,13 +151,8 @@ class observable_unique_ptr_base { template void set_this_observer_(U* p) noexcept { if constexpr (std::is_convertible_v) { - using input_observer_type = std::decay_tthis_observer)>; - using observer_element_type = typename input_observer_type::element_type; - static_assert(std::is_convertible_v, - "incompatible type specified in enable_observer_from_this"); - if (p) { - p->this_observer.set_data_(block, p); + p->this_control_block = block; ++block->refcount; } } @@ -876,6 +900,9 @@ class observer_ptr { // Friendship is required for enable_observer_from_this. template friend class details::observable_unique_ptr_base; + // Friendship is required for enable_observer_from_this. + template + friend class enable_observer_from_this; using control_block = details::control_block; @@ -898,6 +925,13 @@ class observer_ptr { data = d; } + // For enable_observer_from_this + observer_ptr(control_block* b, T* d) noexcept : block(b), data(d) { + if (block) { + ++block->refcount; + } + } + public: /// Type of the pointed object using element_type = T; @@ -1206,20 +1240,46 @@ bool operator!= (const observer_ptr& first, const observer_ptr& second) no * **Corner cases.** * - If a class A inherits from both another class B and enable_observer_from_this, * and it is owned by an observable_unique_ptr. The function observer_from_this() -* will always return nullptr if ownership is taken from a pointer to B*, but will -* be a valid pointer if ownership is taken from a pointer to A*. +* will always return nullptr if ownership is acquired from a pointer to B*, but will +* return a valid pointer if ownership is acquired from a pointer to A*. Therefore, +* make sure to always acquire ownership on the most derived type, or simply use the +* factory function make_observable_unique() which will enforce this automatically. +* +* struct B { +* virtual ~B() = default; +* }; * -* observable_unique_ptr p(new A); // observer pointer is valid -* observable_unique_ptr p(static_cast(new A)); // observer pointer is nullptr +* struct A : B, enable_observer_from_this {}; +* +* +* observable_unique_ptr good1(new A); +* dynamic_cast(good1.get())->observer_from_this(); // valid pointer +* +* observable_unique_ptr good2(make_observable_unique()); +* dynamic_cast(good2.get())->observer_from_this(); // valid pointer +* +* // Bad: do not do this +* observable_unique_ptr bad(static_cast(new A)); +* dynamic_cast(bad.get())->observer_from_this(); // nullptr +* +* - Multiple inheritance. If a class A inherits from both another class B and +* enable_observer_from_this, and if B also inherits from +* enable_observer_from_this, then observer_from_this() will be an ambiguous +* call. But it can be resolved, and (contrary to std::shared_ptr and +* std::enable_shared_from_this) will return a valid pointer: +* +* struct B : enable_observer_from_this { +* virtual ~B() = default; +* }; +* +* struct A : B, enable_observer_from_this {}; +* +* observable_sealed_ptr ptr = make_observable_sealed(); +* ptr->enable_observer_from_this::observer_from_this(); // valid A* pointer +* ptr->enable_observer_from_this::observer_from_this(); // valid B* pointer */ 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; - +class enable_observer_from_this : public virtual details::enable_observer_from_this_base { protected: enable_observer_from_this() noexcept = default; @@ -1233,10 +1293,30 @@ class enable_observer_from_this : public details::enable_observer_from_this_base // invalid reference. }; - ~enable_observer_from_this() noexcept = default; + ~enable_observer_from_this() noexcept { + if (this_control_block) { + pop_ref_(); + } + + this_control_block = nullptr; + } + + enable_observer_from_this& operator=(const enable_observer_from_this&) noexcept { + // Do not copy the other object's observer, this would be an + // invalid reference. + return *this; + }; + + enable_observer_from_this& operator=(enable_observer_from_this&&) noexcept { + // Do not move the other object's observer, this would be an + // invalid reference. + return *this; + }; public: + using observer_element_type = T; + /// 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 @@ -1244,7 +1324,8 @@ class enable_observer_from_this : public details::enable_observer_from_this_base * type of smart pointer, then this function will return nullptr. */ observer_ptr observer_from_this() { - return this_observer; + return observer_ptr{this_control_block, + this_control_block ? static_cast(this) : nullptr}; } /// Return a const observer pointer to 'this'. @@ -1254,7 +1335,8 @@ class enable_observer_from_this : public details::enable_observer_from_this_base * type of smart pointer, then this function will return nullptr. */ observer_ptr observer_from_this() const { - return this_observer; + return observer_ptr{this_control_block, + this_control_block ? static_cast(this) : nullptr}; } }; diff --git a/tests/runtime_tests.cpp b/tests/runtime_tests.cpp index dba77df..00553fb 100644 --- a/tests/runtime_tests.cpp +++ b/tests/runtime_tests.cpp @@ -3245,3 +3245,29 @@ TEST_CASE("observer from this heap", "[observer_from_this]") { REQUIRE(mem_track.leaks() == 0u); REQUIRE(mem_track.double_del() == 0u); } + +TEST_CASE("observer from this multiple inheritance", "[observer_from_this]") { + memory_tracker mem_track; + + { + using this_base = oup::enable_observer_from_this; + using this_deriv = oup::enable_observer_from_this; + + test_object_observer_from_this_multi* raw_ptr_deriv = new test_object_observer_from_this_multi; + test_object_observer_from_this* raw_ptr_base = raw_ptr_deriv; + test_ptr_from_this_multi ptr(raw_ptr_deriv); + + test_optr_from_this optr_from_this_base = ptr->this_base::observer_from_this(); + test_optr_from_this_multi optr_from_this_deriv = ptr->this_deriv::observer_from_this(); + + REQUIRE(instances == 1); + REQUIRE(optr_from_this_base.expired() == false); + REQUIRE(optr_from_this_deriv.expired() == false); + REQUIRE(optr_from_this_base.get() == raw_ptr_base); + REQUIRE(optr_from_this_deriv.get() == raw_ptr_deriv); + } + + 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 bf11de1..386b597 100644 --- a/tests/tests_common.hpp +++ b/tests/tests_common.hpp @@ -45,6 +45,10 @@ struct test_object_observer_from_this : struct test_object_observer_from_this_derived : public test_object_observer_from_this {}; +struct test_object_observer_from_this_multi : + public test_object_observer_from_this, + public oup::enable_observer_from_this {}; + struct test_deleter { int state_ = 0; @@ -86,6 +90,8 @@ using test_cptr_from_this = oup::observable_unique_ptr; using test_ptr_from_this_derived = oup::observable_unique_ptr; using test_sptr_from_this_derived = oup::observable_sealed_ptr; +using test_ptr_from_this_multi = oup::observable_unique_ptr; +using test_sptr_from_this_multi = oup::observable_sealed_ptr; using test_optr = oup::observer_ptr; using test_optr_derived = oup::observer_ptr; @@ -93,3 +99,5 @@ 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; +using test_optr_from_this_multi = oup::observer_ptr; +using test_optr_from_this_multi_const = oup::observer_ptr; From 84cf5db9378ef9afe4bb00e12b09dee494b325ff Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 12:35:00 +0000 Subject: [PATCH 04/12] Clean up documentation --- doc/dox.conf | 4 +- include/oup/observable_unique_ptr.hpp | 266 +++++++++++++------------- 2 files changed, 135 insertions(+), 135 deletions(-) diff --git a/doc/dox.conf b/doc/dox.conf index a9f2baa..52c3100 100644 --- a/doc/dox.conf +++ b/doc/dox.conf @@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = observable_unique_ptr +PROJECT_NAME = "Observable unique-ownership smart pointers" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -2235,7 +2235,7 @@ DIA_PATH = # and usage relations if the target is undocumented or is not a class. # The default value is: YES. -HIDE_UNDOC_RELATIONS = YES +HIDE_UNDOC_RELATIONS = NO # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: diff --git a/include/oup/observable_unique_ptr.hpp b/include/oup/observable_unique_ptr.hpp index 45a07d1..f302de5 100644 --- a/include/oup/observable_unique_ptr.hpp +++ b/include/oup/observable_unique_ptr.hpp @@ -143,10 +143,10 @@ 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. + /// Fill in the observer pointer for objects inheriting from `enable_observer_from_this`. /** \note It is important to preserve the type of the pointer as supplied by the user. - * It might be of a derived type that inherits from enable_observer_from_this, while - * the base type T might not. We still want to fill in the observer pointer if we can. + * It might be of a derived type that inherits from `enable_observer_from_this`, while + * the base type `T` might not. We still want to fill in the observer pointer if we can. */ template void set_this_observer_(U* p) noexcept { @@ -240,7 +240,7 @@ class observable_unique_ptr_base { * \note After this smart pointer is created, the source * pointer is set to null and looses ownership. The source deleter * is moved. This constructor only takes part in overload resolution - * if D is convertible to Deleter and U* is convertible to T*. + * if `D` is convertible to `Deleter` and `U*` is convertible to `T*`. */ template observable_unique_ptr_base(observable_unique_ptr_base&& value) noexcept : @@ -301,7 +301,7 @@ class observable_unique_ptr_base { * \note After this smart pointer is created, the source * pointer is set to null and looses ownership. The source deleter * is moved. This operator only takes part in overload resolution - * if D is convertible to Deleter and U* is convertible to T*. + * if `D` is convertible to `Deleter` and `U*` is convertible to `T*`. */ template observable_unique_ptr_base& operator=(observable_unique_ptr_base&& value) noexcept { @@ -324,8 +324,6 @@ class observable_unique_ptr_base { /// Returns the deleter object which would be used for destruction of the managed object. /** \return The deleter - * \note Using the return value of this function if has_deleter() returns 'false' will cause - * undefined behavior. */ Deleter& get_deleter() noexcept { return ptr_deleter; @@ -333,8 +331,6 @@ class observable_unique_ptr_base { /// Returns the deleter object which would be used for destruction of the managed object. /** \return The deleter - * \note Using the return value of this function if has_deleter() returns 'false' will cause - * undefined behavior. */ const Deleter& get_deleter() const noexcept { return ptr_deleter; @@ -354,7 +350,7 @@ class observable_unique_ptr_base { } /// Replaces the managed object with a null pointer. - /** \param ptr A nullptr_t instance + /** \param ptr A `nullptr_t` instance */ void reset(std::nullptr_t ptr = nullptr) noexcept { if (ptr_deleter.data) { @@ -364,11 +360,11 @@ class observable_unique_ptr_base { } } - /// Get a non-owning raw pointer to the pointed object, or nullptr if deleted. - /** \return 'nullptr' if expired() is 'true', or the pointed object otherwise - * \note Contrary to std::weak_ptr::lock(), this does not extend the lifetime - * of the pointed object. Therefore, when calling this function, you must - * make sure that the owning observable_unique_ptr will not be reset until + /// Get a non-owning raw pointer to the pointed object, or `nullptr` if deleted. + /** \return `nullptr` if `expired()` is `true`, or the pointed object otherwise + * \note This does not extend the lifetime of the pointed object. + * Therefore, when calling this function, you must + * make sure that the owning pointer will not be reset until * you are done using the raw pointer. */ T* get() const noexcept { @@ -377,17 +373,21 @@ class observable_unique_ptr_base { /// Get a reference to the pointed object (undefined behavior if deleted). /** \return A reference to the pointed object - * \note Using this function if expired() is 'true' will leave to undefined behavior. + * \note Using this function if `expired()` is `true` will lead to undefined behavior. + * This does not extend the lifetime of the pointed object. + * Therefore, when calling this function, you must + * make sure that the owning pointer will not be reset until + * you are done using the raw pointer. */ T& operator*() const noexcept { return *ptr_deleter.data; } - /// Get a non-owning raw pointer to the pointed object, or nullptr if deleted. - /** \return 'nullptr' if expired() is 'true', or the pointed object otherwise - * \note Contrary to std::weak_ptr::lock(), this does not extend the lifetime - * of the pointed object. Therefore, when calling this function, you must - * make sure that the owning observable_unique_ptr will not be reset until + /// Get a non-owning raw pointer to the pointed object, or `nullptr` if deleted. + /** \return `nullptr` if no object is owned, or the pointed object otherwise + * \note This does not extend the lifetime of the pointed object. + * Therefore, when calling this function, you must + * make sure that the owning pointer will not be reset until * you are done using the raw pointer. */ T* operator->() const noexcept { @@ -395,7 +395,7 @@ class observable_unique_ptr_base { } /// Check if this pointer points to a valid object. - /** \return 'true' if the pointed object is valid, 'false' otherwise + /** \return `true` if the pointed object is valid, 'false' otherwise */ explicit operator bool() noexcept { return ptr_deleter.data != nullptr; @@ -404,28 +404,28 @@ class observable_unique_ptr_base { } -/// Unique-ownership smart pointer, can be observed by observer_ptr, ownership can be released. -/** This smart pointer mimics the interface of std::unique_ptr, in that +/// Unique-ownership smart pointer, can be observed by `observer_ptr`, ownership can be released. +/** This smart pointer mimics the interface of `std::unique_ptr`, in that * it is movable but not copiable. The smart pointer holds exclusive * (unique) ownership of the pointed object, but allows the ownership to * be released, e.g., to transfer the ownership to another owner. * -* The main difference with std::unique_ptr is that it allows creating -* observer_ptr instances to observe the lifetime of the pointed object, -* as one would do with std::shared_ptr and std::weak_ptr. The price to pay, -* compared to a standard std::unique_ptr, is the additional heap allocation -* of the reference-counting control block. Because observable_unique_ptr -* can be released (see release()), this cannot be optimized. If releasing -* is not a needed feature, consider using observable_sealed_ptr instead. +* The main difference with `std::unique_ptr` is that it allows creating +* `observer_ptr` instances to observe the lifetime of the pointed object, +* as one would do with `std::shared_ptr` and `std::weak_ptr`. The price to pay, +* compared to a standard `std::unique_ptr`, is the additional heap allocation +* of the reference-counting control block. Because `observable_unique_ptr` +* can be released (see `release()`), this cannot be optimized. If releasing +* is not a needed feature, consider using `observable_sealed_ptr` instead. * * Other notable points (either limitations imposed by the current * implementation, or features not implemented simply because of lack of * motivation): -* - because of the unique ownership, observer_ptr cannot extend -* the lifetime of the pointed object, hence observable_unique_ptr provides +* - because of the unique ownership, `observer_ptr` cannot extend +* the lifetime of the pointed object, hence `observable_unique_ptr` provides * less thread-safety compared to std::shared_ptr. -* - observable_unique_ptr does not support arrays. -* - observable_unique_ptr does not allow custom allocators. +* - `observable_unique_ptr` does not support arrays. +* - `observable_unique_ptr` does not allow custom allocators. */ template> class observable_unique_ptr : @@ -471,8 +471,8 @@ class observable_unique_ptr : /// Explicit ownership capture of a raw pointer. /** \param value The raw pointer to take ownership of * \note Do *not* manually delete this raw pointer after the - * observable_unique_ptr is created. If possible, prefer - * using make_observable_unique() instead of this constructor. + * `observable_unique_ptr` is created. If possible, prefer + * using `make_observable_unique()` instead of this constructor. */ template>> explicit observable_unique_ptr(U* value) : @@ -484,8 +484,8 @@ class observable_unique_ptr : /** \param value The raw pointer to take ownership of * \param del The deleter object to use * \note Do *not* manually delete this raw pointer after the - * observable_unique_ptr is created. If possible, prefer - * using make_observable_unique() instead of this constructor. + * `observable_unique_ptr` is created. If possible, prefer + * using `make_observable_unique()` instead of this constructor. */ template>> explicit observable_unique_ptr(U* value, Deleter del) : @@ -506,7 +506,7 @@ class observable_unique_ptr : * \note After this observable_unique_ptr is created, the source * pointer is set to null and looses ownership. The source deleter * is moved. This constructor only takes part in overload resolution - * if D is convertible to Deleter and U* is convertible to T*. + * if `D` is convertible to `Deleter` and `U*` is convertible to `T*`. */ template && std::is_convertible_v>> @@ -543,7 +543,7 @@ class observable_unique_ptr : /// Transfer ownership by implicit casting /** \param value The pointer to take ownership from - * \note After this observable_unique_ptr is created, the source + * \note After this `observable_unique_ptr` is created, the source * pointer is set to null and looses ownership. */ observable_unique_ptr& operator=(observable_unique_ptr&& value) noexcept { @@ -553,10 +553,10 @@ class observable_unique_ptr : /// Transfer ownership by implicit casting /** \param value The pointer to take ownership from - * \note After this observable_unique_ptr is created, the source + * \note After this `observable_unique_ptr` is created, the source * pointer is set to null and looses ownership. The source deleter * is moved. This operator only takes part in overload resolution - * if D is convertible to Deleter and U* is convertible to T*. + * if `D` is convertible to `Deleter` and `U*` is convertible to `T*`. */ template && std::is_convertible_v>> @@ -584,7 +584,7 @@ class observable_unique_ptr : using base::operator bool; /// Replaces the managed object. - /** \param ptr A nullptr_t instance + /** \param ptr The new object to manage (can be `nullptr`, then this is equivalent to `reset()`) */ template>> void reset(U* ptr) noexcept { @@ -597,7 +597,7 @@ class observable_unique_ptr : base::ptr_deleter.data = ptr; // Delete the old pointer - // (this follows std::unique_ptr specs) + // (this follows `std::unique_ptr` specs) if (old_ptr) { base::delete_and_pop_ref_(old_block, old_ptr, base::ptr_deleter); } @@ -607,7 +607,7 @@ class observable_unique_ptr : /// Releases ownership of the managed object and mark observers as expired. /** \return A pointer to the un-managed object - * \note The returned pointer, if not nullptr, becomes owned by the caller and + * \note The returned pointer, if not `nullptr`, becomes owned by the caller and * must be either manually deleted, or managed by another shared pointer. * Existing observer pointers will see the object as expired. */ @@ -628,29 +628,29 @@ class observable_unique_ptr : friend observable_unique_ptr make_observable_unique(Args&& ... args); }; -/// Unique-ownership smart pointer, can be observed by observer_ptr, ownership cannot be released -/** This smart pointer mimics the interface of std::unique_ptr, in that +/// Unique-ownership smart pointer, can be observed by `observer_ptr`, ownership cannot be released +/** This smart pointer mimics the interface of `std::unique_ptr`, in that * it is movable but not copiable. The smart pointer holds exclusive * (unique) ownership of the pointed object. Once ownership is acquired, it * cannot be released. If this becomes necessary, consider using observable_unique_ptr * instead. * -* The main difference with std::unique_ptr is that it allows creating -* observer_ptr instances to observe the lifetime of the pointed object, -* as one would do with std::shared_ptr and std::weak_ptr. The price to pay, -* compared to a standard std::unique_ptr, is the additional heap allocation -* of the reference-counting control block, which make_observable_sealed() +* The main difference with `std::unique_ptr` is that it allows creating +* `observer_ptr` instances to observe the lifetime of the pointed object, +* as one would do with `std::shared_ptr` and `std::weak_ptr`. The price to pay, +* compared to a standard `std::unique_ptr`, is the additional heap allocation +* of the reference-counting control block, which `make_observable_sealed()` * will optimize as a single heap allocation with the pointed object (as -* std::make_shared() does for std::shared_ptr). +* `std::make_shared()` does for `std::shared_ptr`). * * Other notable points (either limitations imposed by the current * implementation, or features not implemented simply because of lack of * motivation): -* - because of the unique ownership, observer_ptr cannot extend -* the lifetime of the pointed object, hence observable_sealed_ptr provides -* less thread-safety compared to std::shared_ptr. -* - observable_sealed_ptr does not support arrays. -* - observable_sealed_ptr does not allow custom allocators. +* - because of the unique ownership, `observer_ptr` cannot extend +* the lifetime of the pointed object, hence `observable_sealed_ptr` provides +* less thread-safety compared to `std::shared_ptr`. +* - `observable_sealed_ptr` does not support arrays. +* - `observable_sealed_ptr` does not allow custom allocators. */ template class observable_sealed_ptr : @@ -697,23 +697,23 @@ class observable_sealed_ptr : ~observable_sealed_ptr() noexcept = default; /// Explicit ownership capture of a raw pointer is forbidden. - /** \note If you need to do this, use observable_unique_ptr instead. + /** \note If you need to do this, use `observable_unique_ptr` instead. */ template explicit observable_sealed_ptr(U*) = delete; /// Transfer ownership by implicit casting /** \param value The pointer to take ownership from - * \note After this observable_sealed_ptr is created, the source + * \note After this `observable_sealed_ptr` is created, the source * pointer is set to null and looses ownership. */ observable_sealed_ptr(observable_sealed_ptr&& value) noexcept = default; /// Transfer ownership by implicit casting /** \param value The pointer to take ownership from - * \note After this observable_sealed_ptr is created, the source + * \note After this `observable_sealed_ptr` is created, the source * pointer is set to null and looses ownership. This constructor - * only takes part in overload resolution if U* is convertible to T*. + * only takes part in overload resolution if `U*` is convertible to `T*`. */ template>> @@ -723,7 +723,7 @@ class observable_sealed_ptr : /// Transfer ownership by explicit casting /** \param manager The smart pointer to take ownership from * \param value The casted pointer value to take ownership of - * \note After this observable_sealed_ptr is created, the source + * \note After this `observable_sealed_ptr` is created, the source * pointer is set to null and looses ownership. */ template @@ -732,7 +732,7 @@ class observable_sealed_ptr : /// Transfer ownership by implicit casting /** \param value The pointer to take ownership from - * \note After this observable_sealed_ptr is created, the source + * \note After this `observable_sealed_ptr` is created, the source * pointer is set to null and looses ownership. */ observable_sealed_ptr& operator=(observable_sealed_ptr&& value) noexcept { @@ -742,9 +742,9 @@ class observable_sealed_ptr : /// Transfer ownership by implicit casting /** \param value The pointer to take ownership from - * \note After this observable_sealed_ptr is created, the source + * \note After this `observable_sealed_ptr` is created, the source * pointer is set to null and looses ownership. This operator only takes - * part in overload resolution if U* is convertible to T*. + * part in overload resolution if `U*` is convertible to `T*`. */ template>> @@ -775,15 +775,15 @@ class observable_sealed_ptr : friend observable_sealed_ptr make_observable_sealed(Args&& ... args); }; -/// Create a new observable_unique_ptr with a newly constructed object. +/// Create a new `observable_unique_ptr` with a newly constructed object. /** \param args Arguments to construct the new object * \return The new observable_unique_ptr * \note Custom deleters are not supported by this function. If you require -* a custom deleter, please use the observable_unique_ptr constructors -* directly. Compared to make_observable_sealed(), this function +* a custom deleter, please use the `observable_unique_ptr` constructors +* directly. Compared to `make_observable_sealed()`, this function * does not allocate the pointed object and the control block in a single -* buffer, as that would prevent writing observable_unique_ptr::release(). -* If releasing the pointer is not needed, consider using observable_sealed_ptr +* buffer, as that would prevent writing `observable_unique_ptr::release()`. +* If releasing the pointer is not needed, consider using `observable_sealed_ptr` * instead. */ template @@ -791,10 +791,10 @@ observable_unique_ptr make_observable_unique(Args&& ... args) { return observable_unique_ptr(new T(std::forward(args)...)); } -/// Create a new observable_sealed_ptr with a newly constructed object. +/// Create a new `observable_sealed_ptr` with a newly constructed object. /** \param args Arguments to construct the new object * \return The new observable_sealed_ptr -* \note This function is the only way to create an observable_sealed_ptr. +* \note This function is the only way to create an `observable_sealed_ptr`. * It will allocate the pointed object and the control block in a * single buffer for better performance. */ @@ -887,7 +887,7 @@ bool operator!= (const observable_sealed_ptr& first, return first.get() != second.get(); } -/// Non-owning smart pointer that observes an observable_unique_ptr or an observable_sealed_ptr. +/// Non-owning smart pointer that observes an `observable_unique_ptr` or an `observable_sealed_ptr`. /** \see observable_unique_ptr * \see observable_sealed_ptr */ @@ -951,7 +951,7 @@ class observer_ptr { } } - /// Create a weak pointer from an owning pointer. + /// Create an observer pointer from an owning pointer. template>> observer_ptr(const observable_unique_ptr& owner) noexcept : block(owner.block), data(owner.ptr_deleter.data) { @@ -960,7 +960,7 @@ class observer_ptr { } } - /// Create a weak pointer from an owning pointer. + /// Create an observer pointer from an owning pointer. template>> observer_ptr(const observable_sealed_ptr& owner) noexcept : block(owner.block), data(owner.ptr_deleter.data) { @@ -969,8 +969,8 @@ class observer_ptr { } } - /// Copy an existing observer_ptr instance - /** \param value The existing weak pointer to copy + /// Copy an existing `observer_ptr` instance + /** \param value The existing observer pointer to copy */ observer_ptr(const observer_ptr& value) noexcept : block(value.block), data(value.data) { @@ -979,8 +979,8 @@ class observer_ptr { } } - /// Copy an existing observer_ptr instance - /** \param value The existing weak pointer to copy + /// Copy an existing `observer_ptr` instance + /** \param value The existing observer pointer to copy */ template>> observer_ptr(const observer_ptr& value) noexcept : @@ -990,9 +990,9 @@ class observer_ptr { } } - /// Move from an existing observer_ptr instance - /** \param value The existing weak pointer to move from - * \note After this observer_ptr is created, the source + /// Move from an existing `observer_ptr` instance + /** \param value The existing observer pointer to move from + * \note After this `observer_ptr` is created, the source * pointer is set to null. */ observer_ptr(observer_ptr&& value) noexcept : block(value.block), data(value.data) { @@ -1000,12 +1000,11 @@ class observer_ptr { value.data = nullptr; } - /// Move from an existing observer_ptr instance - /** \param value The existing weak pointer to move from - * \note After this observer_ptr is created, the source + /// Move from an existing `observer_ptr` instance + /** \param value The existing observer pointer to move from + * \note After this `observer_ptr` is created, the source * pointer is set to null. This constructor only takes part in - * overload resolution if D is convertible to Deleter and U* is - * convertible to T*. + * overload resolution if U* is convertible to T*. */ template>> observer_ptr(observer_ptr&& value) noexcept : block(value.block), data(value.data) { @@ -1015,8 +1014,8 @@ class observer_ptr { /// Point to another owning pointer. /** \param owner The new owner pointer to observe - * \note This operator only takes part in overload resolution if D - * is convertible to Deleter and U* is convertible to T*. + * \note This operator only takes part in overload resolution if + * `U*` is convertible to `T*`. */ template>> observer_ptr& operator=(const observable_unique_ptr& owner) noexcept { @@ -1031,8 +1030,8 @@ class observer_ptr { /// Point to another owning pointer. /** \param owner The new owner pointer to observe - * \note This operator only takes part in overload resolution if D - * is convertible to Deleter and U* is convertible to T*. + * \note This operator only takes part in overload resolution if + * `U*` is convertible to `T*`. */ template>> observer_ptr& operator=(const observable_sealed_ptr& owner) noexcept { @@ -1045,7 +1044,7 @@ class observer_ptr { return *this; } - /// Copy an existing observer_ptr instance + /// Copy an existing `observer_ptr` instance /** \param value The existing weak pointer to copy */ observer_ptr& operator=(const observer_ptr& value) noexcept { @@ -1062,10 +1061,10 @@ class observer_ptr { return *this; } - /// Copy an existing observer_ptr instance + /// Copy an existing `observer_ptr` instance /** \param value The existing weak pointer to copy - * \note This operator only takes part in overload resolution if D - * is convertible to Deleter and U* is convertible to T*. + * \note This operator only takes part in overload resolution if + * `U*` is convertible to `T*`. */ template>> observer_ptr& operator=(const observer_ptr& value) noexcept { @@ -1082,10 +1081,9 @@ class observer_ptr { return *this; } - /// Move from an existing observer_ptr instance + /// Move from an existing `observer_ptr` instance /** \param value The existing weak pointer to move from - * \note After the assignment is complete, the source - * pointer is set to null and looses ownership. + * \note After the assignment is complete, the source pointer is set to null. */ observer_ptr& operator=(observer_ptr&& value) noexcept { set_data_(value.block, value.data); @@ -1096,12 +1094,11 @@ class observer_ptr { return *this; } - /// Move from an existing observer_ptr instance + /// Move from an existing `observer_ptr` instance /** \param value The existing weak pointer to move from - * \note After the assignment is complete, the source - * pointer is set to null and looses ownership. - * This operator only takes part in overload resolution if D - * is convertible to Deleter and U* is convertible to T*. + * \note After the assignment is complete, the source pointer is set to null. + * This operator only takes part in overload resolution if + * `U*` is convertible to `T*`. */ template>> observer_ptr& operator=(observer_ptr&& value) noexcept { @@ -1122,8 +1119,8 @@ class observer_ptr { } } - /// Get a non-owning raw pointer to the pointed object, or nullptr if deleted. - /** \return 'nullptr' if expired() is 'true', or the pointed object otherwise + /// Get a non-owning raw pointer to the pointed object, or `nullptr` if deleted. + /** \return `nullptr` if `expired()` is `true`, or the pointed object otherwise * \note This does not extend the lifetime of the pointed object. Therefore, when * calling this function, you must make sure that the owning pointer * will not be reset until you are done using the raw pointer. @@ -1147,32 +1144,31 @@ class observer_ptr { /// Get a reference to the pointed object (undefined behavior if deleted). /** \return A reference to the pointed object - * \note Using this function if expired() is 'true' will leave to undefined behavior. + * \note Using this function if `expired()` is `true` will lead to undefined behavior. */ T& operator*() const noexcept { return *get(); } - /// Get a non-owning raw pointer to the pointed object, or nullptr if deleted. - /** \return 'nullptr' if expired() is 'true', or the pointed object otherwise - * \note Contrary to std::weak_ptr::lock(), this does not extend the lifetime - * of the pointed object. Therefore, when calling this function, you must - * make sure that the owning pointer will not be reset until - * you are done using the raw pointer. + /// Get a non-owning raw pointer to the pointed object, or `nullptr` if deleted. + /** \return `nullptr` if `expired()` is `true`, or the pointed object otherwise + * \note This does not extend the lifetime of the pointed object. Therefore, when + * calling this function, you must make sure that the owning pointer + * will not be reset until you are done using the raw pointer. */ T* operator->() const noexcept { return get(); } /// Check if this pointer points to a valid object. - /** \return 'true' if the pointed object is valid, 'false' otherwise + /** \return `true` if the pointed object is valid, 'false' otherwise */ bool expired() const noexcept { return block == nullptr || block->expired(); } /// Check if this pointer points to a valid object. - /** \return 'true' if the pointed object is valid, 'false' otherwise + /** \return `true` if the pointed object is valid, 'false' otherwise */ explicit operator bool() noexcept { return block != nullptr && !block->expired(); @@ -1225,26 +1221,27 @@ bool operator!= (const observer_ptr& first, const observer_ptr& second) no return first.get() != second.get(); } -/// Enables creating an observer pointer from 'this'. +/// 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 +* 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. +* type of smart pointer, then `observer_from_this()` will return nullptr. * * **Corner cases.** -* - If a class A inherits from both another class B and enable_observer_from_this, -* and it is owned by an observable_unique_ptr. The function observer_from_this() -* will always return nullptr if ownership is acquired from a pointer to B*, but will -* return a valid pointer if ownership is acquired from a pointer to A*. Therefore, +* - If a class `A` inherits from both another class `B` and `enable_observer_from_this`, +* and it is owned by an `observable_unique_ptr`. The function `observer_from_this()` +* will always return `nullptr` if ownership is acquired from a p`B*`, but will +* return a valid pointer if ownership is acquired from a `A*`. Therefore, * make sure to always acquire ownership on the most derived type, or simply use the -* factory function make_observable_unique() which will enforce this automatically. +* factory function `make_observable_unique()` which will enforce this automatically. * +* ``` * struct B { * virtual ~B() = default; * }; @@ -1253,21 +1250,23 @@ bool operator!= (const observer_ptr& first, const observer_ptr& second) no * * * observable_unique_ptr good1(new A); -* dynamic_cast(good1.get())->observer_from_this(); // valid pointer +* dynamic_cast(good1.get())->observer_from_this(); // valid A* * * observable_unique_ptr good2(make_observable_unique()); -* dynamic_cast(good2.get())->observer_from_this(); // valid pointer +* dynamic_cast(good2.get())->observer_from_this(); // valid A* * * // Bad: do not do this * observable_unique_ptr bad(static_cast(new A)); * dynamic_cast(bad.get())->observer_from_this(); // nullptr +* ``` * -* - Multiple inheritance. If a class A inherits from both another class B and -* enable_observer_from_this, and if B also inherits from -* enable_observer_from_this, then observer_from_this() will be an ambiguous -* call. But it can be resolved, and (contrary to std::shared_ptr and -* std::enable_shared_from_this) will return a valid pointer: +* - Multiple inheritance. If a class `A` inherits from both another class `B` and +* `enable_observer_from_this`, and if `B` also inherits from +* `enable_observer_from_this`, then `observer_from_this()` will be an ambiguous +* call. But it can be resolved, and (contrary to `std::shared_ptr` and +* `std::enable_shared_from_this`) will return a valid pointer: * +* ``` * struct B : enable_observer_from_this { * virtual ~B() = default; * }; @@ -1275,8 +1274,9 @@ bool operator!= (const observer_ptr& first, const observer_ptr& second) no * struct A : B, enable_observer_from_this {}; * * observable_sealed_ptr ptr = make_observable_sealed(); -* ptr->enable_observer_from_this::observer_from_this(); // valid A* pointer -* ptr->enable_observer_from_this::observer_from_this(); // valid B* pointer +* ptr->enable_observer_from_this::observer_from_this(); // valid A* +* ptr->enable_observer_from_this::observer_from_this(); // valid B* +* ``` */ template class enable_observer_from_this : public virtual details::enable_observer_from_this_base { From 9383b8d231ba9e4b48aae7da6001d491568a2d53 Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 12:41:42 +0000 Subject: [PATCH 05/12] Added compile-time checks for incorrect inheritance --- include/oup/observable_unique_ptr.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/oup/observable_unique_ptr.hpp b/include/oup/observable_unique_ptr.hpp index f302de5..17f5f1f 100644 --- a/include/oup/observable_unique_ptr.hpp +++ b/include/oup/observable_unique_ptr.hpp @@ -1324,6 +1324,9 @@ class enable_observer_from_this : public virtual details::enable_observer_from_t * type of smart pointer, then this function will return nullptr. */ observer_ptr observer_from_this() { + static_assert(std::is_base_of_v>, + "T must inherit from enable_observer_from_this"); + return observer_ptr{this_control_block, this_control_block ? static_cast(this) : nullptr}; } @@ -1335,6 +1338,9 @@ class enable_observer_from_this : public virtual details::enable_observer_from_t * type of smart pointer, then this function will return nullptr. */ observer_ptr observer_from_this() const { + static_assert(std::is_base_of_v>, + "T must inherit from enable_observer_from_this"); + return observer_ptr{this_control_block, this_control_block ? static_cast(this) : nullptr}; } From 40ce05551341b8c7db886790e2df1d6ab6590184 Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 12:45:05 +0000 Subject: [PATCH 06/12] Fixed observable_unique_ptr::reset(T*) incorrectly marked noexcept --- include/oup/observable_unique_ptr.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/oup/observable_unique_ptr.hpp b/include/oup/observable_unique_ptr.hpp index 17f5f1f..8d7eb28 100644 --- a/include/oup/observable_unique_ptr.hpp +++ b/include/oup/observable_unique_ptr.hpp @@ -587,7 +587,7 @@ class observable_unique_ptr : /** \param ptr The new object to manage (can be `nullptr`, then this is equivalent to `reset()`) */ template>> - void reset(U* ptr) noexcept { + void reset(U* ptr) { // Copy old pointer T* old_ptr = base::ptr_deleter.data; control_block_type* old_block = base::block; From c6ceee8f3b07f9c67fe172d215d2a50d73932c88 Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 12:52:06 +0000 Subject: [PATCH 07/12] Removed un-necessary default template parameter for deleter in details:: --- include/oup/observable_unique_ptr.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/oup/observable_unique_ptr.hpp b/include/oup/observable_unique_ptr.hpp index 8d7eb28..81049bd 100644 --- a/include/oup/observable_unique_ptr.hpp +++ b/include/oup/observable_unique_ptr.hpp @@ -112,7 +112,7 @@ struct enable_observer_from_this_base { friend class observable_unique_ptr_base; }; -template> +template class observable_unique_ptr_base { protected: using control_block_type = details::control_block; From 41a7cbab3e764a13d0a1be657d97bfcf150b9168 Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 13:02:35 +0000 Subject: [PATCH 08/12] Added explicit checks against T[], which is not supported --- include/oup/observable_unique_ptr.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/oup/observable_unique_ptr.hpp b/include/oup/observable_unique_ptr.hpp index 81049bd..45e9929 100644 --- a/include/oup/observable_unique_ptr.hpp +++ b/include/oup/observable_unique_ptr.hpp @@ -450,6 +450,8 @@ class observable_unique_ptr : friend base; public: + static_assert(!std::is_array_v, "T[] is not supported"); + using typename base::element_type; using typename base::observer_type; using typename base::pointer; @@ -682,6 +684,8 @@ class observable_sealed_ptr : friend base; public: + static_assert(!std::is_array_v, "T[] is not supported"); + using typename base::element_type; using typename base::observer_type; using typename base::pointer; @@ -933,6 +937,8 @@ class observer_ptr { } public: + static_assert(!std::is_array_v, "T[] is not supported"); + /// Type of the pointed object using element_type = T; From 49a5c3c479efcddc212454f48302a475718952b1 Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 13:15:09 +0000 Subject: [PATCH 09/12] Fix clang not used as compiler in CI --- .github/workflows/cmake.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index bbf8a38..b5fef72 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -13,8 +13,8 @@ jobs: fail-fast: false matrix: platform: - - { name: Ubuntu GCC, os: ubuntu-latest, compiler: g++, arch: "64", cmakepp: "", flags: "-DCMAKE_CXX_FLAGS=--coverage"} - - { name: Ubuntu Clang, os: ubuntu-latest, compiler: clang++, arch: "64", cmakepp: "", flags: ""} + - { name: Ubuntu GCC, os: ubuntu-latest, compiler: g++, arch: "64", cmakepp: "", flags: "-DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS=--coverage"} + - { name: Ubuntu Clang, os: ubuntu-latest, compiler: clang++, arch: "64", cmakepp: "", flags: "-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS=-stdlib=libc++"} - { name: Windows 32, os: windows-latest, compiler: vs2019, arch: "32", cmakepp: "", flags: "-A Win32"} - { name: Windows 64, os: windows-latest, compiler: vs2019, arch: "64", cmakepp: "", flags: "-A x64"} - { name: MacOS, os: macos-latest, compiler: clang++, arch: "64", cmakepp: "", flags: ""} @@ -32,10 +32,6 @@ jobs: with: submodules: 'recursive' - - name: Setup Linux compiler - if: runner.os == 'Linux' - run: export CXX=${{matrix.platform.compiler}} - - name: Setup Emscripten cache if: matrix.platform.compiler == 'em++' id: cache-system-libraries From 81ce40a20914867421dab35183b9ef1feb706347 Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 13:17:44 +0000 Subject: [PATCH 10/12] Added missing apt install libc++-dev --- .github/workflows/cmake.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index b5fef72..49881d4 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -32,6 +32,10 @@ jobs: with: submodules: 'recursive' + - name: Setup Clang + if: matrix.platform.compiler == 'clang++' && matrix.platform.os == 'ubuntu-latest' + run: sudo apt install libc++-dev + - name: Setup Emscripten cache if: matrix.platform.compiler == 'em++' id: cache-system-libraries From a54357149601e009a944d207eea8ba13777ad56f Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 13:29:11 +0000 Subject: [PATCH 11/12] Added more items to clang setup --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 49881d4..16cf524 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Clang if: matrix.platform.compiler == 'clang++' && matrix.platform.os == 'ubuntu-latest' - run: sudo apt install libc++-dev + run: sudo apt install clang libc++-dev libc++abi-dev - name: Setup Emscripten cache if: matrix.platform.compiler == 'em++' From 726c5a7613fa87e55c4cd0dd8fb7e17f4c9cb50d Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 13 Nov 2021 13:53:32 +0000 Subject: [PATCH 12/12] Fix missing coverage of deleting control block in enable_observer_from_this --- tests/runtime_tests.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/runtime_tests.cpp b/tests/runtime_tests.cpp index 00553fb..d209908 100644 --- a/tests/runtime_tests.cpp +++ b/tests/runtime_tests.cpp @@ -3157,16 +3157,20 @@ TEST_CASE("observer from this after release", "[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(); + { + 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); + 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); + } + // The object holds the last reference to the control block delete ptr2; + REQUIRE(instances == 0); } REQUIRE(instances == 0);