Skip to content

Commit 47ccff3

Browse files
committed
Fixed release() not expiring the observers, and fixed releasing pointers
created with make_observable_unique.
1 parent b2d3fc1 commit 47ccff3

File tree

2 files changed

+89
-35
lines changed

2 files changed

+89
-35
lines changed

include/oup/observable_unique_ptr.hpp

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,22 @@ class observer_ptr;
1212

1313
namespace details {
1414
struct control_block {
15+
enum flag_elements {
16+
flag_none = 0,
17+
flag_placement_allocated = 1,
18+
flag_expired = 2,
19+
flag_released = 4
20+
};
21+
1522
int refcount = 1;
16-
bool placement_allocated = false;
17-
bool expired = false;
23+
int flags = flag_none;
24+
25+
bool placement_allocated() const { return flags & flag_placement_allocated; }
26+
bool expired() const { return flags & flag_expired; }
27+
bool released() const { return flags & flag_released; }
28+
29+
void set_expired() { flags = flags | flag_expired; }
30+
void set_released() { flags = flags | flag_released; }
1831
};
1932
}
2033

@@ -61,12 +74,12 @@ class observable_unique_ptr {
6174
static void pop_ref_(control_block* block) noexcept {
6275
--block->refcount;
6376
if (block->refcount == 0) {
64-
if constexpr (std::is_same_v<Deleter, std::default_delete<T>>) {
65-
if (block->placement_allocated) {
77+
if (block->placement_allocated()) {
78+
if (block->released()) {
6679
block->~control_block();
67-
delete[] reinterpret_cast<std::byte*>(block);
6880
} else {
69-
delete block;
81+
block->~control_block();
82+
delete[] (reinterpret_cast<std::byte*>(block) - sizeof(T));
7083
}
7184
} else {
7285
delete block;
@@ -75,17 +88,13 @@ class observable_unique_ptr {
7588
}
7689

7790
static void delete_and_pop_ref_(control_block* block, T* data, Deleter& deleter) noexcept {
78-
if constexpr (std::is_same_v<Deleter, std::default_delete<T>>) {
79-
if (block->placement_allocated) {
80-
data->~T();
81-
} else {
82-
deleter(data);
83-
}
91+
if (block->placement_allocated()) {
92+
data->~T();
8493
} else {
8594
deleter(data);
8695
}
8796

88-
block->expired = true;
97+
block->set_expired();
8998

9099
pop_ref_(block);
91100
}
@@ -331,12 +340,18 @@ class observable_unique_ptr {
331340
}
332341
}
333342

334-
/// Releases ownership of the managed object.
343+
/// Releases ownership of the managed object and mark observers as expired.
335344
/** \return A pointer to the un-managed object
345+
* \note The returned pointer, if not nullptr, becomes owned by the caller and
346+
* must be either manually deleted, or managed by another shared pointer.
347+
* Existing observer pointers will see the object as expired.
336348
*/
337349
T* release() noexcept {
338350
T* old_ptr = data;
339351
if (data) {
352+
block->set_expired();
353+
block->set_released();
354+
340355
pop_ref_();
341356
block = nullptr;
342357
data = nullptr;
@@ -401,8 +416,9 @@ observable_unique_ptr<T> make_observable_unique(Args&& ... args) {
401416

402417
try {
403418
// Construct control block and object
404-
block_type* block = new (buffer) details::control_block{1, true, false};
405-
T* ptr = new (buffer + block_size) T(std::forward<Args>(args)...);
419+
T* ptr = new (buffer) T(std::forward<Args>(args)...);
420+
block_type* block = new (buffer + object_size) details::control_block{
421+
1, details::control_block::flag_placement_allocated};
406422

407423
// Make owner pointer
408424
return observable_unique_ptr<T>(block, ptr);
@@ -464,9 +480,13 @@ class observer_ptr {
464480
void pop_ref_() noexcept {
465481
--block->refcount;
466482
if (block->refcount == 0) {
467-
if (block->placement_allocated) {
468-
block->~control_block();
469-
delete[] reinterpret_cast<std::byte*>(block);
483+
if (block->placement_allocated()) {
484+
if (block->released()) {
485+
block->~control_block();
486+
} else {
487+
block->~control_block();
488+
delete[] reinterpret_cast<std::byte*>(block);
489+
}
470490
} else {
471491
delete block;
472492
}
@@ -686,14 +706,14 @@ class observer_ptr {
686706
/** \return 'true' if the pointed object is valid, 'false' otherwise
687707
*/
688708
bool expired() const noexcept {
689-
return block == nullptr || block->expired;
709+
return block == nullptr || block->expired();
690710
}
691711

692712
/// Check if this pointer points to a valid object.
693713
/** \return 'true' if the pointed object is valid, 'false' otherwise
694714
*/
695715
explicit operator bool() noexcept {
696-
return block != nullptr && !block->expired;
716+
return block != nullptr && !block->expired();
697717
}
698718

699719
/// Swap the content of this pointer with that of another pointer.

tests/runtime_tests.cpp

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -815,12 +815,41 @@ TEST_CASE("owner release valid", "[owner_utility]") {
815815
memory_tracker mem_track;
816816

817817
{
818-
test_ptr ptr(new test_object);
819-
test_object* ptr_raw = ptr.release();
820-
REQUIRE(ptr_raw != nullptr);
821-
REQUIRE(ptr.get() == nullptr);
822-
REQUIRE(instances == 1);
823-
delete ptr_raw;
818+
test_optr optr;
819+
{
820+
test_ptr ptr(new test_object);
821+
optr = ptr;
822+
test_object* ptr_raw = ptr.release();
823+
REQUIRE(ptr_raw != nullptr);
824+
REQUIRE(ptr.get() == nullptr);
825+
REQUIRE(instances == 1);
826+
delete ptr_raw;
827+
}
828+
829+
REQUIRE(optr.expired());
830+
}
831+
832+
REQUIRE(instances == 0);
833+
REQUIRE(mem_track.leaks() == 0u);
834+
REQUIRE(mem_track.double_del() == 0u);
835+
}
836+
837+
TEST_CASE("owner release valid from make_observable_unique", "[owner_utility]") {
838+
memory_tracker mem_track;
839+
840+
{
841+
test_optr optr;
842+
{
843+
test_ptr ptr = oup::make_observable_unique<test_object>();
844+
optr = ptr;
845+
test_object* ptr_raw = ptr.release();
846+
REQUIRE(ptr_raw != nullptr);
847+
REQUIRE(ptr.get() == nullptr);
848+
REQUIRE(instances == 1);
849+
delete ptr_raw;
850+
}
851+
852+
REQUIRE(optr.expired());
824853
}
825854

826855
REQUIRE(instances == 0);
@@ -832,14 +861,19 @@ TEST_CASE("owner release valid with deleter", "[owner_utility]") {
832861
memory_tracker mem_track;
833862

834863
{
835-
test_ptr_with_deleter ptr(new test_object, test_deleter{42});
836-
test_object* ptr_raw = ptr.release();
837-
REQUIRE(ptr_raw != nullptr);
838-
REQUIRE(ptr.get() == nullptr);
839-
REQUIRE(instances == 1);
840-
REQUIRE(instances_deleter == 1);
841-
REQUIRE(ptr.get_deleter().state_ == 42);
842-
delete ptr_raw;
864+
test_optr optr;
865+
{
866+
test_ptr_with_deleter ptr(new test_object, test_deleter{42});
867+
test_object* ptr_raw = ptr.release();
868+
REQUIRE(ptr_raw != nullptr);
869+
REQUIRE(ptr.get() == nullptr);
870+
REQUIRE(instances == 1);
871+
REQUIRE(instances_deleter == 1);
872+
REQUIRE(ptr.get_deleter().state_ == 42);
873+
delete ptr_raw;
874+
}
875+
876+
REQUIRE(optr.expired());
843877
}
844878

845879
REQUIRE(instances == 0);

0 commit comments

Comments
 (0)