Skip to content

Add enable_observer_from_this and fix self assignment/swap #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 11, 2021
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ int main() {
}
```

As with `std::shared_ptr`/`std::weak_ptr`, if you need to obtain an observer pointer to an object when you only have `this` (i.e., from a member function), you can inherit from `oup::enable_observer_from_this<T>` to gain access to the `observer_from_this()` member function. This function will return a valid observer pointer as long as the object is owned by a unique or sealed pointer, and will return `nullptr` in all other cases.


## Limitations

Expand Down Expand Up @@ -192,4 +194,4 @@ Detail of the benchmarks:

## Alternative implementation

An alternative implementation of an "observable unique pointer" can be found [here](https://www.codeproject.com/articles/1011134/smart-observers-to-use-with-unique-ptr). It does not compile out of the box with gcc unfortunately, but it does contain more features (like creating an observer pointer from a raw `this`) and lacks others (their `make_observable` always performs two allocations). Have a look to check if this better suits your needs.
An alternative implementation of an "observable unique pointer" can be found [here](https://www.codeproject.com/articles/1011134/smart-observers-to-use-with-unique-ptr). It does not compile out of the box with gcc unfortunately and lacks certain features (their `make_observable` always performs two allocations). Have a look to check if this better suits your needs.
180 changes: 129 additions & 51 deletions include/oup/observable_unique_ptr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ namespace oup {
template<typename T>
class observer_ptr;

template<typename T>
class enable_observer_from_this;

namespace details {

struct control_block {
enum flag_elements {
flag_none = 0,
Expand All @@ -34,6 +38,7 @@ template<typename T, typename Deleter>
struct ptr_and_deleter : Deleter {
T* data = nullptr;
};

}

/// Simple default deleter
Expand Down Expand Up @@ -75,6 +80,9 @@ struct placement_delete
};

namespace details {

struct enable_observer_from_this_base {};

template<typename T, typename Deleter = oup::default_delete<T>>
class observable_unique_ptr_base {
protected:
Expand Down Expand Up @@ -106,6 +114,16 @@ class observable_unique_ptr_base {
delete_and_pop_ref_(block, ptr_deleter.data, ptr_deleter);
}

/// Fill in the observer pointer for objects inheriting from enable_observer_from_this.
void set_this_observer_() noexcept {
if constexpr (std::is_base_of_v<details::enable_observer_from_this_base, T>) {
if (ptr_deleter.data) {
ptr_deleter.data->this_observer.set_data_(block, ptr_deleter.data);
++block->refcount;
}
}
}

/// Private constructor using pre-allocated control block.
/** \param ctrl The control block pointer
* \param value The pointer to own
Expand Down Expand Up @@ -292,6 +310,10 @@ class observable_unique_ptr_base {
/** \param other The other pointer to swap with
*/
void swap(observable_unique_ptr_base& other) noexcept {
if (&other == this) {
return;
}

using std::swap;
swap(block, other.block);
swap(ptr_deleter, other.ptr_deleter);
Expand Down Expand Up @@ -345,6 +367,7 @@ class observable_unique_ptr_base {
return ptr_deleter.data != nullptr;
}
};

}

/// Unique-ownership smart pointer, can be observed by observer_ptr, ownership can be released.
Expand Down Expand Up @@ -381,21 +404,6 @@ class observable_unique_ptr :
return new control_block_type;
}

static void pop_ref_(control_block_type* block) noexcept {
--block->refcount;
if (block->refcount == 0) {
delete block;
}
}

static void delete_and_pop_ref_(control_block_type* block, T* data, Deleter& deleter) noexcept {
deleter(data);

block->set_expired();

pop_ref_(block);
}

// Friendship is required for conversions.
template<typename U>
friend class observer_ptr;
Expand Down Expand Up @@ -433,7 +441,9 @@ class observable_unique_ptr :
* using make_observable_unique() instead of this constructor.
*/
explicit observable_unique_ptr(T* value) :
base(value != nullptr ? allocate_block_() : nullptr, value) {}
base(value != nullptr ? allocate_block_() : nullptr, value) {
base::set_this_observer_();
}

/// Explicit ownership capture of a raw pointer, with customer deleter.
/** \param value The raw pointer to take ownership of
Expand All @@ -443,7 +453,9 @@ class observable_unique_ptr :
* using make_observable_unique() instead of this constructor.
*/
explicit observable_unique_ptr(T* value, Deleter del) :
base(value != nullptr ? allocate_block_() : nullptr, value, std::move(del)) {}
base(value != nullptr ? allocate_block_() : nullptr, value, std::move(del)) {
base::set_this_observer_();
}

/// Transfer ownership by implicit casting
/** \param value The pointer to take ownership from
Expand Down Expand Up @@ -474,7 +486,9 @@ class observable_unique_ptr :
*/
template<typename U, typename D>
observable_unique_ptr(observable_unique_ptr<U,D>&& manager, T* value) noexcept :
base(std::move(manager), value) {}
base(std::move(manager), value) {
base::set_this_observer_();
}

/// Transfer ownership by explicit casting
/** \param manager The smart pointer to take ownership from
Expand All @@ -485,7 +499,9 @@ class observable_unique_ptr :
*/
template<typename U, typename D>
observable_unique_ptr(observable_unique_ptr<U,D>&& manager, T* value, Deleter del) noexcept :
base(std::move(manager), value, del) {}
base(std::move(manager), value, del) {
base::set_this_observer_();
}

/// Transfer ownership by implicit casting
/** \param value The pointer to take ownership from
Expand Down Expand Up @@ -544,8 +560,10 @@ class observable_unique_ptr :
// Delete the old pointer
// (this follows std::unique_ptr specs)
if (old_ptr) {
delete_and_pop_ref_(old_block, old_ptr, base::ptr_deleter);
base::delete_and_pop_ref_(old_block, old_ptr, base::ptr_deleter);
}

base::set_this_observer_();
}

/// Releases ownership of the managed object and mark observers as expired.
Expand Down Expand Up @@ -608,7 +626,9 @@ class observable_sealed_ptr :
* \note This is used by make_observable_sealed().
*/
observable_sealed_ptr(control_block_type* ctrl, T* value) noexcept :
base(ctrl, value, oup::placement_delete<T>{}) {}
base(ctrl, value, oup::placement_delete<T>{}) {
base::set_this_observer_();
}

// Friendship is required for conversions.
template<typename U>
Expand Down Expand Up @@ -744,7 +764,7 @@ observable_sealed_ptr<T> make_observable_sealed(Args&& ... args) {
// Allocate memory
constexpr std::size_t block_size = sizeof(block_type);
constexpr std::size_t object_size = sizeof(T);
std::byte* buffer = new std::byte[block_size + object_size];
std::byte* buffer = reinterpret_cast<std::byte*>(operator new(block_size + object_size));

try {
// Construct control block and object
Expand All @@ -756,7 +776,7 @@ observable_sealed_ptr<T> make_observable_sealed(Args&& ... args) {
} catch (...) {
// Exception thrown during object construction,
// clean up memory and let exception propagate
delete[] buffer;
delete buffer;
throw;
}
}
Expand Down Expand Up @@ -835,6 +855,9 @@ class observer_ptr {
// Friendship is required for conversions.
template<typename U>
friend class observer_ptr;
// Friendship is required for enable_observer_from_this.
template<typename U, typename D>
friend class details::observable_unique_ptr_base;

using control_block = details::control_block;

Expand All @@ -848,6 +871,15 @@ class observer_ptr {
}
}

void set_data_(control_block* b, T* d) noexcept {
if (data) {
pop_ref_();
}

block = b;
data = d;
}

public:
/// Type of the pointed object
using element_type = T;
Expand Down Expand Up @@ -936,12 +968,8 @@ class observer_ptr {
*/
template<typename U, typename D, typename enable = std::enable_if_t<std::is_convertible_v<U*, T*>>>
observer_ptr& operator=(const observable_unique_ptr<U,D>& owner) noexcept {
if (data) {
pop_ref_();
}
set_data_(owner.block, owner.ptr_deleter.data);

block = owner.block;
data = owner.ptr_deleter.data;
if (block) {
++block->refcount;
}
Expand All @@ -956,12 +984,8 @@ class observer_ptr {
*/
template<typename U, typename enable = std::enable_if_t<std::is_convertible_v<U*, T*>>>
observer_ptr& operator=(const observable_sealed_ptr<U>& owner) noexcept {
if (data) {
pop_ref_();
}
set_data_(owner.block, owner.ptr_deleter.data);

block = owner.block;
data = owner.ptr_deleter.data;
if (block) {
++block->refcount;
}
Expand All @@ -973,12 +997,12 @@ class observer_ptr {
/** \param value The existing weak pointer to copy
*/
observer_ptr& operator=(const observer_ptr& value) noexcept {
if (data) {
pop_ref_();
if (&value == this) {
return *this;
}

block = value.block;
data = value.data;
set_data_(value.block, value.data);

if (block) {
++block->refcount;
}
Expand All @@ -993,12 +1017,12 @@ class observer_ptr {
*/
template<typename U, typename enable = std::enable_if_t<std::is_convertible_v<U*, T*>>>
observer_ptr& operator=(const observer_ptr<U>& value) noexcept {
if (data) {
pop_ref_();
if (&value == this) {
return *this;
}

block = value.block;
data = value.data;
set_data_(value.block, value.data);

if (block) {
++block->refcount;
}
Expand All @@ -1012,13 +1036,9 @@ class observer_ptr {
* pointer is set to null and looses ownership.
*/
observer_ptr& operator=(observer_ptr&& value) noexcept {
if (data) {
pop_ref_();
}
set_data_(value.block, value.data);

block = value.block;
value.block = nullptr;
data = value.data;
value.data = nullptr;

return *this;
Expand All @@ -1033,13 +1053,9 @@ class observer_ptr {
*/
template<typename U, typename enable = std::enable_if_t<std::is_convertible_v<U*, T*>>>
observer_ptr& operator=(observer_ptr<U>&& value) noexcept {
if (data) {
pop_ref_();
}
set_data_(value.block, value.data);

block = value.block;
value.block = nullptr;
data = value.data;
value.data = nullptr;

return *this;
Expand Down Expand Up @@ -1114,6 +1130,10 @@ class observer_ptr {
/** \param other The other pointer to swap with
*/
void swap(observer_ptr& other) noexcept {
if (&other == this) {
return;
}

using std::swap;
swap(block, other.block);
swap(data, other.data);
Expand Down Expand Up @@ -1153,6 +1173,64 @@ bool operator!= (const observer_ptr<T>& first, const observer_ptr<U>& second) no
return first.get() != second.get();
}

/// Enables creating an observer pointer from 'this'.
/** If an object must be able to create an observer pointer to itself,
* without having direct access to the owner pointer (unique or sealed),
* then the object's class can inherit from enable_observer_from_this.
* This provides the observer_from_this() member function, which returns
* a new observer pointer to the object. For this mechanism to work,
* the class must inherit publicly from enable_observer_from_this,
* and the object must be owned by a unique or sealed pointer when
* calling observer_from_this(). If the latter condition is not satisfied,
* i.e., the object was allocated on the stack, or is owned by another
* type of smart pointer, then observer_from_this() will return nullptr.
*/
template<typename T>
class enable_observer_from_this : public details::enable_observer_from_this_base {
mutable observer_ptr<T> this_observer;

// Friendship is required for assignment of the observer.
template<typename U, typename D>
friend class details::observable_unique_ptr_base;

protected:
enable_observer_from_this() noexcept = default;

enable_observer_from_this(const enable_observer_from_this&) noexcept {
// Do not copy the other object's observer, this would be an
// invalid reference.
};

enable_observer_from_this(enable_observer_from_this&&) noexcept {
// Do not move the other object's observer, this would be an
// invalid reference.
};

~enable_observer_from_this() noexcept = default;

public:

/// Return an observer pointer to 'this'.
/** \return A new observer pointer pointing to 'this'.
* \note If 'this' is not owned by a unique or sealed pointer, i.e., if
* the object was allocated on the stack, or if it is owned by another
* type of smart pointer, then this function will return nullptr.
*/
observer_ptr<T> observer_from_this() {
return this_observer;
}

/// Return a const observer pointer to 'this'.
/** \return A new observer pointer pointing to 'this'.
* \note If 'this' is not owned by a unique or sealed pointer, i.e., if
* the object was allocated on the stack, or if it is owned by another
* type of smart pointer, then this function will return nullptr.
*/
observer_ptr<const T> observer_from_this() const {
return this_observer;
}
};

}

#endif
Loading