Skip to content

Commit 14ba848

Browse files
committed
Work in progress on custom implementation
1 parent 5eacb7a commit 14ba848

File tree

1 file changed

+150
-105
lines changed

1 file changed

+150
-105
lines changed

include/oup/observable_unique_ptr.hpp

Lines changed: 150 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,10 @@
22
#define OBSERVABLE_UNIQUE_PTR_INCLUDED
33

44
#include <memory>
5+
#include <cstddef>
56

67
namespace oup {
78

8-
/// Simple deleter, suitable for objects allocated with new.
9-
struct default_deleter {
10-
template<typename T>
11-
void operator() (T* ptr) noexcept { delete ptr; }
12-
13-
void operator() (std::nullptr_t) noexcept {}
14-
};
15-
169
template<typename T>
1710
class observer_ptr;
1811

@@ -46,76 +39,90 @@ class observer_ptr;
4639
* has_deleter() before calling get_deleter(), or use try_get_deleter().
4740
* - a moved-from observable_unique_ptr will not own a deleter instance.
4841
*/
49-
template<typename T, typename Deleter = default_deleter>
50-
class observable_unique_ptr : private std::shared_ptr<T> {
42+
template<typename T, typename Deleter = std::default_delete<T>>
43+
class observable_unique_ptr {
5144
private:
52-
/// Construct from shared_ptr.
53-
/** \param value The shared_ptr to take ownership from
54-
* \note This is private since the use of std::shared_ptr is
55-
* an implementation detail.
56-
*/
57-
explicit observable_unique_ptr(std::shared_ptr<T>&& value) noexcept :
58-
std::shared_ptr<T>(std::move(value)) {}
59-
6045
// Friendship is required for conversions.
6146
template<typename U, typename D>
6247
friend class observable_unique_ptr;
6348

64-
// Friendship is required for access to std::shared_ptr base
65-
template<typename U>
66-
friend class observer_ptr;
49+
struct control_block {
50+
std::size_t refcount = 0u;
51+
bool placement_allocated = false;
52+
};
6753

68-
public:
69-
/// Type of the pointed object
70-
using typename std::shared_ptr<T>::element_type;
54+
control_block* block = nullptr;
55+
T* pointer = nullptr;
56+
Deleter deleter;
7157

72-
/// Type of the matching observer pointer
73-
using observer_type = observer_ptr<T>;
58+
control_block* allocate_block_(T* value) {
59+
return new control_block{std::size_t{1u}};
60+
}
7461

75-
/// Get a non-owning raw pointer to the pointed object, or nullptr if none.
76-
/** \return 'nullptr' if no object is owned, or the pointed object otherwise
77-
*/
78-
using std::shared_ptr<T>::get;
62+
void pop_ref_() noexcept {
63+
deleter(pointer);
7964

80-
/// Get a reference to the pointed object (undefined behavior if nullptr).
81-
/** \return A reference to the pointed object
82-
* \note Using this function if this pointer is null will leave to undefined behavior.
83-
*/
84-
using std::shared_ptr<T>::operator*;
65+
--block->refcount;
66+
if (block->refcount == 0u) {
67+
if (block->placement_allocated) {
68+
block->~control_block();
69+
delete[] static_cast<std::byte*>(block);
70+
} else {
71+
delete block;
72+
}
73+
}
74+
}
8575

86-
/// Get a non-owning raw pointer to the pointed object, or nullptr if none.
87-
/** \return 'nullptr' if no object is owned, or the pointed object otherwise
76+
/// Private constructor using pre-allocated control block.
77+
/** \param ctrl The control block pointer
78+
* \param value The pointer to own
79+
* \note This is used by make_observable_unique().
8880
*/
89-
using std::shared_ptr<T>::operator->;
81+
observable_unique_ptr(control_block* ctrl, T* value) : block(ctrl), pointer(value) {}
9082

91-
/// Check if this pointer points to a valid object.
92-
/** \return 'true' if the pointed object is valid, 'false' otherwise
83+
/// Private constructor using pre-allocated control block.
84+
/** \param ctrl The control block pointer
85+
* \param value The pointer to own
86+
* \note This is used by make_observable_unique().
9387
*/
94-
using std::shared_ptr<T>::operator bool;
88+
observable_unique_ptr(control_block* ctrl, T* value, Deleter del) :
89+
block(ctrl), pointer(value), deleter(del) {}
9590

96-
// Define member types for compatibility with std::unique_ptr
91+
// Friendship is required for access to std::shared_ptr base
92+
// template<typename U>
93+
// friend class observer_ptr;
94+
95+
public:
96+
/// Type of the pointed object
97+
using element_type = T;
98+
99+
/// Type of the matching observer pointer
100+
using observer_type = observer_ptr<T>;
101+
102+
/// Pointer type
97103
using pointer = element_type*;
104+
105+
/// Deleter type
98106
using deleter_type = Deleter;
99107

100108
/// Default constructor (null pointer).
101-
observable_unique_ptr() noexcept :
102-
std::shared_ptr<T>(nullptr, Deleter{}) {}
109+
observable_unique_ptr() noexcept = default;
103110

104111
/// Construct a null pointer.
105-
observable_unique_ptr(std::nullptr_t) noexcept :
106-
std::shared_ptr<T>(nullptr, Deleter{}) {}
112+
observable_unique_ptr(std::nullptr_t) noexcept {}
107113

108114
/// Construct a null pointer with custom deleter.
109115
observable_unique_ptr(std::nullptr_t, Deleter deleter) noexcept :
110-
std::shared_ptr<T>(nullptr, std::move(deleter)) {}
116+
deleter(std::move(deleter)) {}
111117

112118
/// Explicit ownership capture of a raw pointer.
113119
/** \param value The raw pointer to take ownership of
114120
* \note Do *not* manually delete this raw pointer after the
115121
* observable_unique_ptr is created. If possible, prefer
116122
* using make_observable_unique() instead of this constructor.
117123
*/
118-
explicit observable_unique_ptr(T* value) : std::shared_ptr<T>(value, Deleter{}) {}
124+
explicit observable_unique_ptr(T* value) :
125+
observable_unique_ptr(allocate_block_(value), value) {}
119126

120127
/// Explicit ownership capture of a raw pointer, with customer deleter.
121128
/** \param value The raw pointer to take ownership of
@@ -124,17 +131,20 @@ class observable_unique_ptr : private std::shared_ptr<T> {
124131
* observable_unique_ptr is created. If possible, prefer
125132
* using make_observable_unique() instead of this constructor.
126133
*/
127-
explicit observable_unique_ptr(T* value, Deleter deleter) :
128-
std::shared_ptr<T>(value, std::move(deleter)) {}
134+
explicit observable_unique_ptr(T* value, Deleter del) :
135+
observable_unique_ptr(allocate_block_(value), value, std::move(del)) {}
129136

130137
/// Transfer ownership by implicit casting
131138
/** \param value The pointer to take ownership from
132139
* \note After this observable_unique_ptr is created, the source
133140
* pointer is set to null and looses ownership.
134141
*/
135-
template<typename U>
142+
template<typename U/*, typename enable = std::enable_if_t<std::is_convertible_v<U, T>>*/>
136143
observable_unique_ptr(observable_unique_ptr<U,Deleter>&& value) noexcept :
137-
std::shared_ptr<T>(std::move(static_cast<std::shared_ptr<U>&>(value))) {}
144+
observable_unique_ptr(value.block, value.pointer, std::move(value.deleter)) {
145+
value.block = nullptr;
146+
value.pointer = nullptr;
147+
}
138148

139149
/// Transfer ownership by explicit casting
140150
/** \param manager The smart pointer to take ownership from
@@ -144,47 +154,42 @@ class observable_unique_ptr : private std::shared_ptr<T> {
144154
*/
145155
template<typename U>
146156
observable_unique_ptr(observable_unique_ptr<U,Deleter>&& manager, T* value) noexcept :
147-
std::shared_ptr<T>(std::move(manager), value) {
148-
manager.std::template shared_ptr<U>::reset();
157+
observable_unique_ptr(manager.block, value, std::move(manager.deleter)) {
158+
manager.block = nullptr;
159+
manager.pointer = nullptr;
149160
}
150161

151162
/// Transfer ownership by implicit casting
152163
/** \param value The pointer to take ownership from
153164
* \note After this observable_unique_ptr is created, the source
154165
* pointer is set to null and looses ownership.
155166
*/
156-
template<typename U>
167+
template<typename U/*, typename enable = std::enable_if_t<std::is_convertible_v<U, T>>*/>
157168
observable_unique_ptr& operator=(observable_unique_ptr<U,Deleter>&& value) noexcept {
158-
std::shared_ptr<T>::operator=(std::move(static_cast<std::shared_ptr<U>&>(value)));
169+
if (pointer) {
170+
pop_ref_();
171+
}
172+
173+
block = value.block;
174+
value.block = nullptr;
175+
pointer = value.pointer;
176+
value.pointer = nullptr;
177+
deleter = std::move(value.deleter);
178+
159179
return *this;
160180
}
161181

162-
// Movable
163-
observable_unique_ptr(observable_unique_ptr&&) noexcept = default;
164-
observable_unique_ptr& operator=(observable_unique_ptr&&) noexcept = default;
165-
166182
// Non-copyable
167183
observable_unique_ptr(const observable_unique_ptr&) = delete;
168184
observable_unique_ptr& operator=(const observable_unique_ptr&) = delete;
169185

170-
/// Checks if this pointer has a custom deleter.
171-
/** \return 'true' if a custom deleter is used, 'false' otherwise.
172-
*/
173-
bool has_deleter() const noexcept {
174-
if constexpr (std::is_same_v<Deleter, oup::default_deleter>) {
175-
return false;
176-
} else {
177-
return std::get_deleter<Deleter>(*this) != nullptr;
178-
}
179-
}
180-
181186
/// Returns the deleter object which would be used for destruction of the managed object.
182187
/** \return The deleter
183188
* \note Using the return value of this function if has_deleter() returns 'false' will cause
184189
* undefined behavior.
185190
*/
186191
Deleter& get_deleter() noexcept {
187-
return *std::get_deleter<Deleter>(*this);
192+
return deleter;
188193
}
189194

190195
/// Returns the deleter object which would be used for destruction of the managed object.
@@ -193,58 +198,42 @@ class observable_unique_ptr : private std::shared_ptr<T> {
193198
* undefined behavior.
194199
*/
195200
const Deleter& get_deleter() const noexcept {
196-
return *std::get_deleter<Deleter>(*this);
197-
}
198-
199-
/// Returns the deleter object which would be used for destruction of the managed object.
200-
/** \return The deleter, or nullptr if no deleter exists
201-
*/
202-
Deleter* try_get_deleter() noexcept {
203-
return std::get_deleter<Deleter>(*this);
204-
}
205-
206-
/// Returns the deleter object which would be used for destruction of the managed object.
207-
/** \return The deleter, or nullptr if no deleter exists
208-
*/
209-
const Deleter* try_get_deleter() const noexcept {
210-
return std::get_deleter<Deleter>(*this);
201+
return deleter;
211202
}
212203

213204
/// Swap the content of this pointer with that of another pointer.
214205
/** \param other The other pointer to swap with
215206
*/
216207
void swap(observable_unique_ptr& other) noexcept {
217-
std::shared_ptr<T>::swap(other);
208+
using std::swap;
209+
swap(block, other.block);
210+
swap(pointer, other.pointer);
211+
swap(deleter, other.deleter);
218212
}
219213

220214
/// Replaces the managed object with a null pointer.
221215
/** \param ptr A nullptr_t instance
222216
*/
223217
void reset(std::nullptr_t ptr = nullptr) noexcept {
224-
if constexpr (std::is_same_v<Deleter, oup::default_deleter>) {
225-
operator=(observable_unique_ptr{ptr});
226-
} else {
227-
if (auto* deleter = try_get_deleter()) {
228-
operator=(observable_unique_ptr{ptr, Deleter{*deleter}});
229-
} else {
230-
operator=(observable_unique_ptr{ptr, Deleter{}});
231-
}
218+
if (pointer) {
219+
pop_ref_();
220+
block = nullptr;
221+
pointer = nullptr;
232222
}
233223
}
234224

235225
/// Replaces the managed object.
236226
/** \param ptr A nullptr_t instance
237227
*/
238228
void reset(T* ptr) noexcept {
239-
if constexpr (std::is_same_v<Deleter, oup::default_deleter>) {
240-
operator=(observable_unique_ptr{ptr});
241-
} else {
242-
if (auto* deleter = try_get_deleter()) {
243-
operator=(observable_unique_ptr{ptr, Deleter{*deleter}});
244-
} else {
245-
operator=(observable_unique_ptr{ptr, Deleter{}});
246-
}
229+
// TODO: copy old ptr, assign new, then delete old
230+
// (to follow std::unique_ptr specs)
231+
if (pointer) {
232+
pop_ref_();
247233
}
234+
235+
block = ptr != nullptr ? allocate_block_() : nullptr;
236+
pointer = ptr;
248237
}
249238

250239
/// Replaces the managed object (with custom deleter).
@@ -256,6 +245,43 @@ class observable_unique_ptr : private std::shared_ptr<T> {
256245
operator=(observable_unique_ptr{ptr, std::move(deleter)});
257246
}
258247

248+
/// Get a non-owning raw pointer to the pointed object, or nullptr if deleted.
249+
/** \return 'nullptr' if expired() is 'true', or the pointed object otherwise
250+
* \note Contrary to std::weak_ptr::lock(), this does not extend the lifetime
251+
* of the pointed object. Therefore, when calling this function, you must
252+
* make sure that the owning observable_unique_ptr will not be reset until
253+
* you are done using the raw pointer.
254+
*/
255+
T* get() const noexcept {
256+
return pointer;
257+
}
258+
259+
/// Get a reference to the pointed object (undefined behavior if deleted).
260+
/** \return A reference to the pointed object
261+
* \note Using this function if expired() is 'true' will leave to undefined behavior.
262+
*/
263+
T& operator*() const noexcept {
264+
return *pointer;
265+
}
266+
267+
/// Get a non-owning raw pointer to the pointed object, or nullptr if deleted.
268+
/** \return 'nullptr' if expired() is 'true', or the pointed object otherwise
269+
* \note Contrary to std::weak_ptr::lock(), this does not extend the lifetime
270+
* of the pointed object. Therefore, when calling this function, you must
271+
* make sure that the owning observable_unique_ptr will not be reset until
272+
* you are done using the raw pointer.
273+
*/
274+
T* operator->() const noexcept {
275+
return pointer;
276+
}
277+
278+
/// Check if this pointer points to a valid object.
279+
/** \return 'true' if the pointed object is valid, 'false' otherwise
280+
*/
281+
explicit operator bool() noexcept {
282+
return pointer != nullptr;
283+
}
284+
259285
template<typename U, typename ... Args>
260286
friend observable_unique_ptr<U> make_observable_unique(Args&& ... args);
261287
};
@@ -266,7 +292,26 @@ class observable_unique_ptr : private std::shared_ptr<T> {
266292
*/
267293
template<typename T, typename ... Args>
268294
observable_unique_ptr<T> make_observable_unique(Args&& ... args) {
269-
return observable_unique_ptr<T>(std::make_shared<T>(std::forward<Args>(args)...));
295+
using block_type = typename observable_unique_ptr<T>::control_block;
296+
297+
// Allocate memory
298+
constexpr std::size_t block_size = sizeof(block_type);
299+
constexpr std::size_t object_size = sizeof(T);
300+
std::byte* buffer = new std::byte[block_size + object_size];
301+
302+
try {
303+
// Construct control block and object
304+
block_type* block = new (buffer) control_block{1u};
305+
T* ptr = new (buffer + block_size) T(std::forward<Args>(args)...);
306+
307+
// Make owner pointer
308+
return observable_unique_ptr<T>(block, ptr, deleter);
309+
} catch (...) {
310+
// Exception thrown durimg object construction,
311+
// clean up memory and let exception propagate
312+
delete[] buffer;
313+
throw;
314+
}
270315
}
271316

272317
template<typename T, typename Deleter>

0 commit comments

Comments
 (0)