diff --git a/.gitignore b/.gitignore index 567609b..146f201 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ build/ +build_clang/ +doc/html/ diff --git a/include/oup/observable_unique_ptr.hpp b/include/oup/observable_unique_ptr.hpp index 45e9929..958ef1d 100644 --- a/include/oup/observable_unique_ptr.hpp +++ b/include/oup/observable_unique_ptr.hpp @@ -258,7 +258,11 @@ class observable_unique_ptr_base { */ template observable_unique_ptr_base(observable_unique_ptr_base&& manager, T* value) noexcept : - observable_unique_ptr_base(manager.block, value) { + observable_unique_ptr_base(value != nullptr ? manager.block : nullptr, value) { + if (manager.ptr_deleter.data != nullptr && value == nullptr) { + manager.delete_and_pop_ref_(); + } + manager.block = nullptr; manager.ptr_deleter.data = nullptr; } @@ -272,7 +276,11 @@ class observable_unique_ptr_base { */ template observable_unique_ptr_base(observable_unique_ptr_base&& manager, T* value, Deleter del) noexcept : - observable_unique_ptr_base(manager.block, value, std::move(del)) { + observable_unique_ptr_base(value != nullptr ? manager.block : nullptr, value, std::move(del)) { + if (manager.ptr_deleter.data != nullptr && value == nullptr) { + manager.delete_and_pop_ref_(); + } + manager.block = nullptr; manager.ptr_deleter.data = nullptr; } @@ -520,7 +528,10 @@ class observable_unique_ptr : * \param value The casted pointer value to take ownership of * \note After this observable_unique_ptr is created, the source * pointer is set to null and looses ownership. The deleter - * is default constructed. + * is default constructed. The raw pointer `value` + * must be obtained by casting the raw pointer managed by `manager` + * (const cast, dynamic cast, etc), such that deleting `value` has + * the same effect as deleting the pointer owned by `manager`. */ template>> @@ -534,7 +545,10 @@ class observable_unique_ptr : * \param value The casted pointer value to take ownership of * \param del The deleter to use in the new pointer * \note After this observable_unique_ptr is created, the source - * pointer is set to null and looses ownership. + * pointer is set to null and looses ownership. The raw pointer `value` + * must be obtained by casting the raw pointer managed by `manager` + * (const cast, dynamic cast, etc), such that deleting `value` has + * the same effect as deleting the pointer owned by `manager`. */ template>> @@ -728,10 +742,14 @@ class observable_sealed_ptr : /** \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 - * pointer is set to null and looses ownership. + * pointer is set to null and looses ownership. The raw pointer `value` + * must be obtained by casting the raw pointer managed by `manager` + * (const cast, dynamic cast, etc), such that deleting `value` has + * the same effect as deleting the pointer owned by `manager`. */ - template - observable_sealed_ptr(observable_sealed_ptr&& manager, T* value) noexcept : + template>> + observable_sealed_ptr(observable_sealed_ptr&& manager, V* value) noexcept : base(std::move(manager), value) {} /// Transfer ownership by implicit casting @@ -996,6 +1014,24 @@ class observer_ptr { } } + /// Copy an existing `observer_ptr` instance with explicit casting + /** \param manager The observer pointer to copy the observed data from + * \param value The casted pointer value to observe + * \note After this smart pointer is created, the source + * pointer is set to null and looses ownership. The deleter + * is default constructed. The raw pointer `value` may or may + * not be related to the raw pointer observed by `manager`. + * This could be a pointer to any other object which is known to + * have the same lifetime. + */ + template + observer_ptr(const observer_ptr& manager, T* value) noexcept : + block(value != nullptr ? manager.block : nullptr), data(value) { + if (block) { + ++block->refcount; + } + } + /// 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 @@ -1018,6 +1054,27 @@ class observer_ptr { value.data = nullptr; } + /// Move from an existing `observer_ptr` instance with explicit casting + /** \param manager The observer pointer to copy the observed data from + * \param value The casted pointer value to observe + * \note After this smart pointer is created, the source + * pointer is set to null and looses ownership. The deleter + * is default constructed. The raw pointer `value` may or may + * not be related to the raw pointer observed by `manager`. + * This could be a pointer to any other object which is known to + * have the same lifetime. + */ + template + observer_ptr(observer_ptr&& manager, T* value) noexcept : + block(value != nullptr ? manager.block : nullptr), data(value) { + if (manager.data != nullptr && value == nullptr) { + manager.pop_ref_(); + } + + manager.block = nullptr; + manager.data = nullptr; + } + /// Point to another owning pointer. /** \param owner The new owner pointer to observe * \note This operator only takes part in overload resolution if @@ -1320,9 +1377,6 @@ class enable_observer_from_this : public virtual details::enable_observer_from_t }; 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 @@ -1352,6 +1406,147 @@ class enable_observer_from_this : public virtual details::enable_observer_from_t } }; +/// Perform a `static_cast` for an `observable_unique_ptr`. +/** \param ptr The pointer to cast +* \note Ownership will be transfered to the returned pointer. + If the input pointer is null, the output pointer will also be null. +*/ +template +observable_unique_ptr static_pointer_cast(observable_unique_ptr&& ptr) { + return observable_unique_ptr(std::move(ptr), static_cast(ptr.get())); +} + +/// Perform a `static_cast` for an `observable_unique_ptr`. +/** \param ptr The pointer to cast +* \note Ownership will be transfered to the returned pointer. + If the input pointer is null, the output pointer will also be null. +*/ +template +observable_sealed_ptr static_pointer_cast(observable_sealed_ptr&& ptr) { + return observable_sealed_ptr(std::move(ptr), static_cast(ptr.get())); +} + +/// Perform a `static_cast` for an `observer_ptr`. +/** \param ptr The pointer to cast +* \note A new observer is returned, the input observer is not modified. + If the input pointer is null, the output pointer will also be null. +*/ +template +observer_ptr static_pointer_cast(const observer_ptr& ptr) { + // NB: can use raw_get() as static cast of an expired pointer is fine + return observer_ptr(ptr, static_cast(ptr.raw_get())); +} + +/// Perform a `static_cast` for an `observer_ptr`. +/** \param ptr The pointer to cast +* \note A new observer is returned, the input observer is set to null. + If the input pointer is null, the output pointer will also be null. +*/ +template +observer_ptr static_pointer_cast(observer_ptr&& ptr) { + // NB: can use raw_get() as static cast of an expired pointer is fine + return observer_ptr(std::move(ptr), static_cast(ptr.raw_get())); +} + +/// Perform a `const_cast` for an `observable_unique_ptr`. +/** \param ptr The pointer to cast +* \note Ownership will be transfered to the returned pointer. + If the input pointer is null, the output pointer will also be null. +*/ +template +observable_unique_ptr const_pointer_cast(observable_unique_ptr&& ptr) { + return observable_unique_ptr(std::move(ptr), const_cast(ptr.get())); +} + +/// Perform a `const_cast` for an `observable_unique_ptr`. +/** \param ptr The pointer to cast +* \note Ownership will be transfered to the returned pointer. + If the input pointer is null, the output pointer will also be null. +*/ +template +observable_sealed_ptr const_pointer_cast(observable_sealed_ptr&& ptr) { + return observable_sealed_ptr(std::move(ptr), const_cast(ptr.get())); +} + +/// Perform a `const_cast` for an `observer_ptr`. +/** \param ptr The pointer to cast +* \note A new observer is returned, the input observer is not modified. + If the input pointer is null, the output pointer will also be null. +*/ +template +observer_ptr const_pointer_cast(const observer_ptr& ptr) { + // NB: can use raw_get() as const cast of an expired pointer is fine + return observer_ptr(ptr, const_cast(ptr.raw_get())); +} + +/// Perform a `const_cast` for an `observer_ptr`. +/** \param ptr The pointer to cast +* \note A new observer is returned, the input observer is set to null. + If the input pointer is null, the output pointer will also be null. +*/ +template +observer_ptr const_pointer_cast(observer_ptr&& ptr) { + // NB: can use raw_get() as const cast of an expired pointer is fine + return observer_ptr(std::move(ptr), const_cast(ptr.raw_get())); +} + +/// Perform a `dynamic_cast` for an `observable_unique_ptr`. +/** \param ptr The pointer to cast +* \note Ownership will be transfered to the returned pointer unless the cast +* fails, in which case ownership remains in the original pointer, std::bad_cast +* is thrown, and no memory is leaked. If the input pointer is null, +* the output pointer will also be null. +*/ +template +observable_unique_ptr dynamic_pointer_cast(observable_unique_ptr&& ptr) { + if (ptr == nullptr) { + return observable_unique_ptr{}; + } + + U& casted_object = dynamic_cast(*ptr.get()); + return observable_unique_ptr(std::move(ptr), &casted_object); +} + +/// Perform a `dynamic_cast` for an `observable_unique_ptr`. +/** \param ptr The pointer to cast +* \note Ownership will be transfered to the returned pointer unless the cast +* fails, in which case ownership remains in the original pointer, and +* no memory is leaked. +*/ +template +observable_sealed_ptr dynamic_pointer_cast(observable_sealed_ptr&& ptr) { + if (ptr == nullptr) { + return observable_sealed_ptr{}; + } + + U& casted_object = dynamic_cast(*ptr.get()); + return observable_sealed_ptr(std::move(ptr), &casted_object); +} + +/// Perform a `dynamic_cast` for an `observer_ptr`. +/** \param ptr The pointer to cast +* \note A new observer is returned, the input observer is not modified. + If the input pointer is null, or if the cast fails, the output pointer + will be null. +*/ +template +observer_ptr dynamic_pointer_cast(const observer_ptr& ptr) { + // NB: must use get() as dynamic cast of an expired pointer is UB + return observer_ptr(ptr, dynamic_cast(ptr.get())); +} + +/// Perform a `dynamic_cast` for an `observer_ptr`. +/** \param ptr The pointer to cast +* \note A new observer is returned, the input observer is set to null. + If the input pointer is null, or if the cast fails, the output pointer + will be null. +*/ +template +observer_ptr dynamic_pointer_cast(observer_ptr&& ptr) { + // NB: must use get() as dynamic cast of an expired pointer is UB + return observer_ptr(std::move(ptr), dynamic_cast(ptr.get())); +} + } #endif diff --git a/tests/runtime_tests.cpp b/tests/runtime_tests.cpp index d209908..b1c88aa 100644 --- a/tests/runtime_tests.cpp +++ b/tests/runtime_tests.cpp @@ -492,6 +492,78 @@ TEST_CASE("owner explicit conversion constructor with custom deleter", "[owner_c REQUIRE(mem_track.double_del() == 0u); } +TEST_CASE("owner explicit conversion constructor with nullptr", "[owner_construction]") { + memory_tracker mem_track; + + { + test_ptr ptr_orig{new test_object_derived}; + { + test_ptr_derived ptr(std::move(ptr_orig), static_cast(nullptr)); + REQUIRE(instances == 0); + REQUIRE(instances_derived == 0); + REQUIRE(ptr.get() == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(instances_derived == 0); + } + + REQUIRE(instances == 0); + REQUIRE(instances_derived == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("owner explicit conversion constructor with nullptr sealed", "[owner_construction]") { + memory_tracker mem_track; + + { + test_sptr ptr_orig = oup::make_observable_sealed(); + { + test_sptr_derived ptr(std::move(ptr_orig), static_cast(nullptr)); + REQUIRE(instances == 0); + REQUIRE(instances_derived == 0); + REQUIRE(ptr.get() == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(instances_derived == 0); + } + + REQUIRE(instances == 0); + REQUIRE(instances_derived == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("owner explicit conversion constructor with nullptr with custom deleter", "[owner_construction]") { + memory_tracker mem_track; + + { + test_ptr_with_deleter ptr_orig{new test_object_derived, test_deleter{42}}; + { + test_ptr_derived_with_deleter ptr(std::move(ptr_orig), + static_cast(nullptr), + test_deleter{43}); + REQUIRE(instances == 0); + REQUIRE(instances_derived == 0); + REQUIRE(instances_deleter == 2); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.get_deleter().state_ == 43); + } + + REQUIRE(instances == 0); + REQUIRE(instances_derived == 0); + REQUIRE(instances_deleter == 1); + } + + REQUIRE(instances == 0); + REQUIRE(instances_derived == 0); + REQUIRE(instances_deleter == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + TEST_CASE("owner move assignment operator valid to empty", "[owner_assignment]") { memory_tracker mem_track; @@ -1762,6 +1834,60 @@ TEST_CASE("observer copy constructor", "[observer_construction]") { REQUIRE(ptr.expired() == false); REQUIRE(ptr_orig.get() != nullptr); REQUIRE(ptr_orig.expired() == false); + + ptr_owner.reset(); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer explicit conversion copy constructor ", "[observer_construction]") { + memory_tracker mem_track; + + { + test_ptr ptr_owner{new test_object_derived}; + test_optr ptr_orig{ptr_owner}; + { + test_optr ptr{ptr_orig, static_cast(ptr_orig.get())}; + REQUIRE(instances == 1); + REQUIRE(ptr.get() == static_cast(ptr_owner.get())); + REQUIRE(ptr.expired() == false); + REQUIRE(ptr_orig.get() == ptr_owner.get()); + REQUIRE(ptr_orig.expired() == false); + + ptr_owner.reset(); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer explicit conversion copy constructor null pointer", "[observer_construction]") { + memory_tracker mem_track; + + { + test_ptr ptr_owner{new test_object_derived}; + test_optr ptr_orig{ptr_owner}; + { + test_optr ptr{ptr_orig, static_cast(nullptr)}; + REQUIRE(instances == 1); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + REQUIRE(ptr_orig.get() == ptr_owner.get()); + REQUIRE(ptr_orig.expired() == false); } REQUIRE(instances == 1); @@ -1772,6 +1898,33 @@ TEST_CASE("observer copy constructor", "[observer_construction]") { REQUIRE(mem_track.double_del() == 0u); } +TEST_CASE("observer explicit conversion copy constructor subobject", "[observer_construction]") { + memory_tracker mem_track; + + { + test_ptr ptr_owner{new test_object_derived}; + test_optr ptr_orig{ptr_owner}; + { + int_optr ptr{ptr_orig, &ptr_owner->state_}; + REQUIRE(instances == 1); + REQUIRE(ptr.get() == &ptr_owner->state_); + REQUIRE(ptr.expired() == false); + REQUIRE(ptr_orig.get() == ptr_owner.get()); + REQUIRE(ptr_orig.expired() == false); + + ptr_owner.reset(); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + TEST_CASE("observer move constructor", "[observer_construction]") { memory_tracker mem_track; @@ -1785,6 +1938,60 @@ TEST_CASE("observer move constructor", "[observer_construction]") { REQUIRE(ptr.expired() == false); REQUIRE(ptr_orig.get() == nullptr); REQUIRE(ptr_orig.expired() == true); + + ptr_owner.reset(); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer explicit conversion move constructor ", "[observer_construction]") { + memory_tracker mem_track; + + { + test_ptr ptr_owner{new test_object_derived}; + test_optr ptr_orig{ptr_owner}; + { + test_optr ptr{std::move(ptr_orig), static_cast(ptr_orig.get())}; + REQUIRE(instances == 1); + REQUIRE(ptr.get() == static_cast(ptr_owner.get())); + REQUIRE(ptr.expired() == false); + REQUIRE(ptr_orig.get() == nullptr); + REQUIRE(ptr_orig.expired() == true); + + ptr_owner.reset(); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("observer explicit conversion move constructor null pointer", "[observer_construction]") { + memory_tracker mem_track; + + { + test_ptr ptr_owner{new test_object_derived}; + test_optr ptr_orig{ptr_owner}; + { + test_optr ptr{std::move(ptr_orig), static_cast(nullptr)}; + REQUIRE(instances == 1); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + REQUIRE(ptr_orig.get() == nullptr); + REQUIRE(ptr_orig.expired() == true); } REQUIRE(instances == 1); @@ -1795,6 +2002,33 @@ TEST_CASE("observer move constructor", "[observer_construction]") { REQUIRE(mem_track.double_del() == 0u); } +TEST_CASE("observer explicit conversion move constructor subobject", "[observer_construction]") { + memory_tracker mem_track; + + { + test_ptr ptr_owner{new test_object_derived}; + test_optr ptr_orig{ptr_owner}; + { + int_optr ptr{std::move(ptr_orig), &ptr_owner->state_}; + REQUIRE(instances == 1); + REQUIRE(ptr.get() == &ptr_owner->state_); + REQUIRE(ptr.expired() == false); + REQUIRE(ptr_orig.get() == nullptr); + REQUIRE(ptr_orig.expired() == true); + + ptr_owner.reset(); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr.expired() == true); + } + + REQUIRE(instances == 0); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + TEST_CASE("observer acquiring constructor", "[observer_construction]") { memory_tracker mem_track; @@ -3275,3 +3509,506 @@ TEST_CASE("observer from this multiple inheritance", "[observer_from_this]") { REQUIRE(mem_track.leaks() == 0u); REQUIRE(mem_track.double_del() == 0u); } + +TEST_CASE("static pointer cast unique from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_object_derived* raw_ptr = new test_object_derived; + test_ptr ptr_orig{raw_ptr}; + test_ptr_derived ptr = oup::static_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("static pointer cast unique from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_ptr ptr_orig; + test_ptr_derived ptr = oup::static_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("static pointer cast sealed from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr_derived ptr_init = oup::make_observable_sealed(); + test_object_derived* raw_ptr = ptr_init.get(); + test_sptr ptr_orig{std::move(ptr_init)}; + test_sptr_derived ptr = oup::static_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("static pointer cast sealed from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr ptr_orig; + test_sptr_derived ptr = oup::static_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("static pointer cast observer copy from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr_derived ptr_owner = oup::make_observable_sealed(); + test_object_derived* raw_ptr = ptr_owner.get(); + test_optr ptr_orig{ptr_owner}; + test_optr_derived ptr = oup::static_pointer_cast(ptr_orig); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig.get() == raw_ptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("static pointer cast observer copy from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_optr ptr_orig; + test_optr_derived ptr = oup::static_pointer_cast(ptr_orig); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("static pointer cast observer move from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr_derived ptr_owner = oup::make_observable_sealed(); + test_object_derived* raw_ptr = ptr_owner.get(); + test_optr ptr_orig{ptr_owner}; + test_optr_derived ptr = oup::static_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("static pointer cast observer move from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_optr ptr_orig; + test_optr_derived ptr = oup::static_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("const pointer cast unique from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_object* raw_ptr = new test_object; + test_ptr_const ptr_orig{raw_ptr}; + test_ptr ptr = oup::const_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("const pointer cast unique from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_ptr_const ptr_orig; + test_ptr ptr = oup::const_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("const pointer cast sealed from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr ptr_init = oup::make_observable_sealed(); + test_object* raw_ptr = ptr_init.get(); + test_sptr_const ptr_orig{std::move(ptr_init)}; + test_sptr ptr = oup::const_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("const pointer cast sealed from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr_const ptr_orig; + test_sptr ptr = oup::const_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("const pointer cast observer copy from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr ptr_owner = oup::make_observable_sealed(); + test_object* raw_ptr = ptr_owner.get(); + test_optr_const ptr_orig{ptr_owner}; + test_optr ptr = oup::const_pointer_cast(ptr_orig); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig.get() == raw_ptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("const pointer cast observer copy from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_optr_const ptr_orig; + test_optr ptr = oup::const_pointer_cast(ptr_orig); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("const pointer cast observer move from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr ptr_owner = oup::make_observable_sealed(); + test_object* raw_ptr = ptr_owner.get(); + test_optr_const ptr_orig{ptr_owner}; + test_optr ptr = oup::const_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("const pointer cast observer move from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_optr_const ptr_orig; + test_optr ptr = oup::const_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast unique from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_object_derived* raw_ptr = new test_object_derived; + test_ptr ptr_orig{raw_ptr}; + test_ptr_derived ptr = oup::dynamic_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast unique from invalid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_ptr ptr_orig{new test_object_observer_from_this}; + + REQUIRE_THROWS_AS( + oup::dynamic_pointer_cast(std::move(ptr_orig)), + std::bad_cast); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig != nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast unique from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_ptr ptr_orig; + test_ptr_derived ptr = oup::dynamic_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast sealed from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr_derived ptr_init = oup::make_observable_sealed(); + test_object_derived* raw_ptr = ptr_init.get(); + test_sptr ptr_orig{std::move(ptr_init)}; + test_sptr_derived ptr = oup::dynamic_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast sealed from invalid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr ptr_orig{oup::make_observable_sealed()}; + + REQUIRE_THROWS_AS( + oup::dynamic_pointer_cast(std::move(ptr_orig)), + std::bad_cast); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig != nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast sealed from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr ptr_orig; + test_sptr_derived ptr = oup::dynamic_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast observer copy from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr_derived ptr_owner = oup::make_observable_sealed(); + test_object_derived* raw_ptr = ptr_owner.get(); + test_optr ptr_orig{ptr_owner}; + test_optr_derived ptr = oup::dynamic_pointer_cast(ptr_orig); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig.get() == raw_ptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast observer copy from invalid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr_from_this ptr_owner = oup::make_observable_sealed(); + test_optr ptr_orig{ptr_owner}; + test_optr_derived ptr = oup::dynamic_pointer_cast(ptr_orig); + + REQUIRE(instances == 1); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr_orig.get() != nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast observer copy from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_optr ptr_orig; + test_optr_derived ptr = oup::dynamic_pointer_cast(ptr_orig); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast observer move from valid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr_derived ptr_owner = oup::make_observable_sealed(); + test_object_derived* raw_ptr = ptr_owner.get(); + test_optr ptr_orig{ptr_owner}; + test_optr_derived ptr = oup::dynamic_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr.get() == raw_ptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast observer move from invalid", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_sptr_from_this ptr_owner = oup::make_observable_sealed(); + test_optr ptr_orig{ptr_owner}; + test_optr_derived ptr = oup::dynamic_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 1); + REQUIRE(ptr.get() == nullptr); + REQUIRE(ptr_orig.get() == nullptr); + } + + REQUIRE(instances == 0); + REQUIRE(mem_track.leaks() == 0u); + REQUIRE(mem_track.double_del() == 0u); +} + +TEST_CASE("dynamic pointer cast observer move from null", "[pointer_cast]") { + memory_tracker mem_track; + + { + test_optr ptr_orig; + test_optr_derived ptr = oup::dynamic_pointer_cast(std::move(ptr_orig)); + + REQUIRE(instances == 0); + REQUIRE(ptr_orig == nullptr); + REQUIRE(ptr == 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 386b597..2f6a1c9 100644 --- a/tests/tests_common.hpp +++ b/tests/tests_common.hpp @@ -77,6 +77,8 @@ struct test_deleter { using test_ptr = oup::observable_unique_ptr; using test_sptr = oup::observable_sealed_ptr; +using test_ptr_const = oup::observable_unique_ptr; +using test_sptr_const = oup::observable_sealed_ptr; using test_ptr_derived = oup::observable_unique_ptr; using test_sptr_derived = oup::observable_sealed_ptr; using test_ptr_with_deleter = oup::observable_unique_ptr; @@ -94,6 +96,7 @@ using test_ptr_from_this_multi = oup::observable_unique_ptr; using test_optr = oup::observer_ptr; +using test_optr_const = 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; @@ -101,3 +104,4 @@ using test_optr_from_this_derived = oup::observer_ptr; using test_optr_from_this_multi = oup::observer_ptr; using test_optr_from_this_multi_const = oup::observer_ptr; +using int_optr = oup::observer_ptr;