Skip to content

Commit b44f93d

Browse files
committed
Ensure we set the "observer pointer from this" in all possible scenarios
1 parent b697b94 commit b44f93d

File tree

3 files changed

+121
-20
lines changed

3 files changed

+121
-20
lines changed

include/oup/observable_unique_ptr.hpp

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,20 @@ class observable_unique_ptr_base {
115115
}
116116

117117
/// Fill in the observer pointer for objects inheriting from enable_observer_from_this.
118-
void set_this_observer_() noexcept {
119-
if constexpr (std::is_base_of_v<details::enable_observer_from_this_base, T>) {
120-
if (ptr_deleter.data) {
121-
ptr_deleter.data->this_observer.set_data_(block, ptr_deleter.data);
118+
/** \note It is important to preserve the type of the pointer as supplied by the user.
119+
* It might be of a derived type that inherits from enable_observer_from_this, while
120+
* the base type T might not. We still want to fill in the observer pointer if we can.
121+
*/
122+
template<typename U>
123+
void set_this_observer_(U* p) noexcept {
124+
if constexpr (std::is_convertible_v<U*,const details::enable_observer_from_this_base*>) {
125+
using input_observer_type = std::decay_t<decltype(p->this_observer)>;
126+
using observer_element_type = typename input_observer_type::element_type;
127+
static_assert(std::is_convertible_v<U*, observer_element_type*>,
128+
"incompatible type specified in enable_observer_from_this");
129+
130+
if (p) {
131+
p->this_observer.set_data_(block, p);
122132
++block->refcount;
123133
}
124134
}
@@ -440,9 +450,10 @@ class observable_unique_ptr :
440450
* observable_unique_ptr is created. If possible, prefer
441451
* using make_observable_unique() instead of this constructor.
442452
*/
443-
explicit observable_unique_ptr(T* value) :
453+
template<typename U, typename enable = std::enable_if_t<std::is_convertible_v<U*,T*>>>
454+
explicit observable_unique_ptr(U* value) :
444455
base(value != nullptr ? allocate_block_() : nullptr, value) {
445-
base::set_this_observer_();
456+
base::set_this_observer_(value);
446457
}
447458

448459
/// Explicit ownership capture of a raw pointer, with customer deleter.
@@ -452,9 +463,10 @@ class observable_unique_ptr :
452463
* observable_unique_ptr is created. If possible, prefer
453464
* using make_observable_unique() instead of this constructor.
454465
*/
455-
explicit observable_unique_ptr(T* value, Deleter del) :
466+
template<typename U, typename enable = std::enable_if_t<std::is_convertible_v<U*,T*>>>
467+
explicit observable_unique_ptr(U* value, Deleter del) :
456468
base(value != nullptr ? allocate_block_() : nullptr, value, std::move(del)) {
457-
base::set_this_observer_();
469+
base::set_this_observer_(value);
458470
}
459471

460472
/// Transfer ownership by implicit casting
@@ -484,10 +496,11 @@ class observable_unique_ptr :
484496
* pointer is set to null and looses ownership. The deleter
485497
* is default constructed.
486498
*/
487-
template<typename U, typename D>
488-
observable_unique_ptr(observable_unique_ptr<U,D>&& manager, T* value) noexcept :
499+
template<typename U, typename D, typename V, typename enable =
500+
std::enable_if_t<std::is_convertible_v<V*,T*>>>
501+
observable_unique_ptr(observable_unique_ptr<U,D>&& manager, V* value) noexcept :
489502
base(std::move(manager), value) {
490-
base::set_this_observer_();
503+
base::set_this_observer_(value);
491504
}
492505

493506
/// Transfer ownership by explicit casting
@@ -497,10 +510,11 @@ class observable_unique_ptr :
497510
* \note After this observable_unique_ptr is created, the source
498511
* pointer is set to null and looses ownership.
499512
*/
500-
template<typename U, typename D>
501-
observable_unique_ptr(observable_unique_ptr<U,D>&& manager, T* value, Deleter del) noexcept :
513+
template<typename U, typename D, typename V, typename enable =
514+
std::enable_if_t<std::is_convertible_v<V*,T*>>>
515+
observable_unique_ptr(observable_unique_ptr<U,D>&& manager, V* value, Deleter del) noexcept :
502516
base(std::move(manager), value, del) {
503-
base::set_this_observer_();
517+
base::set_this_observer_(value);
504518
}
505519

506520
/// Transfer ownership by implicit casting
@@ -548,7 +562,8 @@ class observable_unique_ptr :
548562
/// Replaces the managed object.
549563
/** \param ptr A nullptr_t instance
550564
*/
551-
void reset(T* ptr) noexcept {
565+
template<typename U, typename enable = std::enable_if_t<std::is_convertible_v<U*,T*>>>
566+
void reset(U* ptr) noexcept {
552567
// Copy old pointer
553568
T* old_ptr = base::ptr_deleter.data;
554569
control_block_type* old_block = base::block;
@@ -563,7 +578,7 @@ class observable_unique_ptr :
563578
base::delete_and_pop_ref_(old_block, old_ptr, base::ptr_deleter);
564579
}
565580

566-
base::set_this_observer_();
581+
base::set_this_observer_(ptr);
567582
}
568583

569584
/// Releases ownership of the managed object and mark observers as expired.
@@ -625,9 +640,10 @@ class observable_sealed_ptr :
625640
* \param value The pointer to own
626641
* \note This is used by make_observable_sealed().
627642
*/
628-
observable_sealed_ptr(control_block_type* ctrl, T* value) noexcept :
643+
template<typename U, typename enable = std::enable_if_t<std::is_convertible_v<U*,T*>>>
644+
observable_sealed_ptr(control_block_type* ctrl, U* value) noexcept :
629645
base(ctrl, value, oup::placement_delete<T>{}) {
630-
base::set_this_observer_();
646+
base::set_this_observer_(value);
631647
}
632648

633649
// Friendship is required for conversions.
@@ -659,7 +675,8 @@ class observable_sealed_ptr :
659675
/// Explicit ownership capture of a raw pointer is forbidden.
660676
/** \note If you need to do this, use observable_unique_ptr instead.
661677
*/
662-
explicit observable_sealed_ptr(T*) = delete;
678+
template<typename U>
679+
explicit observable_sealed_ptr(U*) = delete;
663680

664681
/// Transfer ownership by implicit casting
665682
/** \param value The pointer to take ownership from
@@ -760,6 +777,7 @@ observable_unique_ptr<T> make_observable_unique(Args&& ... args) {
760777
template<typename T, typename ... Args>
761778
observable_sealed_ptr<T> make_observable_sealed(Args&& ... args) {
762779
using block_type = typename observable_sealed_ptr<T>::control_block_type;
780+
using decayed_type = std::decay_t<T>;
763781

764782
// Allocate memory
765783
constexpr std::size_t block_size = sizeof(block_type);
@@ -769,7 +787,7 @@ observable_sealed_ptr<T> make_observable_sealed(Args&& ... args) {
769787
try {
770788
// Construct control block and object
771789
block_type* block = new (buffer) block_type;
772-
T* ptr = new (buffer + block_size) T(std::forward<Args>(args)...);
790+
decayed_type* ptr = new (buffer + block_size) decayed_type(std::forward<Args>(args)...);
773791

774792
// Make owner pointer
775793
return observable_sealed_ptr<T>(block, ptr);
@@ -1184,6 +1202,15 @@ bool operator!= (const observer_ptr<T>& first, const observer_ptr<U>& second) no
11841202
* calling observer_from_this(). If the latter condition is not satisfied,
11851203
* i.e., the object was allocated on the stack, or is owned by another
11861204
* type of smart pointer, then observer_from_this() will return nullptr.
1205+
*
1206+
* **Corner cases.**
1207+
* - If a class A inherits from both another class B and enable_observer_from_this<A>,
1208+
* and it is owned by an observable_unique_ptr<B>. The function observer_from_this()
1209+
* will always return nullptr if ownership is taken from a pointer to B*, but will
1210+
* be a valid pointer if ownership is taken from a pointer to A*.
1211+
*
1212+
* observable_unique_ptr<B> p(new A); // observer pointer is valid
1213+
* observable_unique_ptr<B> p(static_cast<B*>(new A)); // observer pointer is nullptr
11871214
*/
11881215
template<typename T>
11891216
class enable_observer_from_this : public details::enable_observer_from_this_base {

tests/runtime_tests.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2983,6 +2983,78 @@ TEST_CASE("observer from this derived", "[observer_from_this]") {
29832983
REQUIRE(mem_track.double_del() == 0u);
29842984
}
29852985

2986+
TEST_CASE("observer from this derived into base", "[observer_from_this]") {
2987+
memory_tracker mem_track;
2988+
2989+
{
2990+
test_object_observer_from_this* orig_ptr = new test_object_observer_from_this;
2991+
test_ptr ptr{orig_ptr};
2992+
2993+
test_optr_from_this optr_from_this = orig_ptr->observer_from_this();
2994+
2995+
REQUIRE(instances == 1);
2996+
REQUIRE(optr_from_this.expired() == false);
2997+
REQUIRE(optr_from_this.get() == ptr.get());
2998+
}
2999+
3000+
REQUIRE(instances == 0);
3001+
REQUIRE(mem_track.leaks() == 0u);
3002+
REQUIRE(mem_track.double_del() == 0u);
3003+
}
3004+
3005+
TEST_CASE("observer from this derived into base after cast", "[observer_from_this]") {
3006+
memory_tracker mem_track;
3007+
3008+
{
3009+
test_object_observer_from_this* orig_ptr = new test_object_observer_from_this;
3010+
test_ptr ptr{static_cast<test_object*>(orig_ptr)};
3011+
3012+
test_optr_from_this optr_from_this = orig_ptr->observer_from_this();
3013+
3014+
REQUIRE(instances == 1);
3015+
REQUIRE(optr_from_this.expired() == true);
3016+
REQUIRE(optr_from_this.get() == nullptr);
3017+
}
3018+
3019+
REQUIRE(instances == 0);
3020+
REQUIRE(mem_track.leaks() == 0u);
3021+
REQUIRE(mem_track.double_del() == 0u);
3022+
}
3023+
3024+
TEST_CASE("observer from this const", "[observer_from_this]") {
3025+
memory_tracker mem_track;
3026+
3027+
{
3028+
test_cptr_from_this ptr{new test_object_observer_from_this};
3029+
test_optr_from_this_const optr_from_this = ptr->observer_from_this();
3030+
3031+
REQUIRE(instances == 1);
3032+
REQUIRE(optr_from_this.expired() == false);
3033+
REQUIRE(optr_from_this.get() == ptr.get());
3034+
}
3035+
3036+
REQUIRE(instances == 0);
3037+
REQUIRE(mem_track.leaks() == 0u);
3038+
REQUIRE(mem_track.double_del() == 0u);
3039+
}
3040+
3041+
TEST_CASE("observer from this const sealed", "[observer_from_this]") {
3042+
memory_tracker mem_track;
3043+
3044+
{
3045+
test_csptr_from_this ptr = oup::make_observable_sealed<const test_object_observer_from_this>();
3046+
test_optr_from_this_const optr_from_this = ptr->observer_from_this();
3047+
3048+
REQUIRE(instances == 1);
3049+
REQUIRE(optr_from_this.expired() == false);
3050+
REQUIRE(optr_from_this.get() == ptr.get());
3051+
}
3052+
3053+
REQUIRE(instances == 0);
3054+
REQUIRE(mem_track.leaks() == 0u);
3055+
REQUIRE(mem_track.double_del() == 0u);
3056+
}
3057+
29863058
TEST_CASE("observer from this after move", "[observer_from_this]") {
29873059
memory_tracker mem_track;
29883060

tests/tests_common.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ using test_sptr_thrower = oup::observable_sealed_ptr<test_object_thrower>;
8282
using test_ptr_thrower_with_deleter = oup::observable_unique_ptr<test_object_thrower,test_deleter>;
8383
using test_ptr_from_this = oup::observable_unique_ptr<test_object_observer_from_this>;
8484
using test_sptr_from_this = oup::observable_sealed_ptr<test_object_observer_from_this>;
85+
using test_cptr_from_this = oup::observable_unique_ptr<const test_object_observer_from_this>;
86+
using test_csptr_from_this = oup::observable_sealed_ptr<const test_object_observer_from_this>;
8587
using test_ptr_from_this_derived = oup::observable_unique_ptr<test_object_observer_from_this_derived>;
8688
using test_sptr_from_this_derived = oup::observable_sealed_ptr<test_object_observer_from_this_derived>;
8789

0 commit comments

Comments
 (0)