Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_SMALL_UNIQUE_PTR_HPP
11 : #define BOOST_CAPY_SMALL_UNIQUE_PTR_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <cstddef>
15 : #include <type_traits>
16 : #include <utility>
17 : #include <new>
18 :
19 : namespace boost {
20 : namespace capy {
21 :
22 : /** A smart pointer with small buffer optimization.
23 :
24 : This class provides unique ownership semantics similar
25 : to `std::unique_ptr`, but with an embedded buffer for
26 : small object optimization. Objects that fit within the
27 : buffer are constructed in-place, avoiding heap allocation.
28 : Larger objects fall back to heap allocation.
29 :
30 : The SBO path requires the managed type to be nothrow
31 : move constructible. Types that do not meet this requirement
32 : or exceed the buffer size are heap-allocated.
33 :
34 : @tparam T The base type of the managed object. Pointers
35 : to derived types may be stored if convertible to `T*`.
36 :
37 : @tparam N The size of the internal buffer in bytes.
38 : Defaults to `sizeof(T)`.
39 :
40 : @par Example
41 : @code
42 : struct Base { virtual ~Base() = default; };
43 : struct Derived : Base { int x; };
44 :
45 : // Uses SBO if Derived fits in buffer
46 : auto p = make_small_unique<Base, 32, Derived>(42);
47 : @endcode
48 :
49 : @see make_small_unique
50 : */
51 : template<class T, std::size_t N = sizeof(T)>
52 : class small_unique_ptr
53 : {
54 : static_assert(N >= sizeof(T), "Buffer too small for base type");
55 :
56 : template<class U>
57 : struct fits_in_buffer
58 : : std::integral_constant<
59 : bool,
60 : (sizeof(U) <= N) &&
61 : (alignof(U) <= alignof(std::max_align_t))>
62 : {
63 : };
64 :
65 : alignas(std::max_align_t) unsigned char buffer_[N];
66 : T* ptr_;
67 : void (*destroy_)(small_unique_ptr*);
68 : void (*relocate_)(small_unique_ptr*, small_unique_ptr*);
69 :
70 : template<class U>
71 6 : static void destroy_small(small_unique_ptr* self)
72 : {
73 6 : static_cast<U*>(self->ptr_)->~U();
74 6 : }
75 :
76 : template<class U>
77 6 : static void destroy_large(small_unique_ptr* self)
78 : {
79 6 : delete static_cast<U*>(self->ptr_);
80 6 : }
81 :
82 : template<class U>
83 5 : static void relocate_small(
84 : small_unique_ptr* src,
85 : small_unique_ptr* dst)
86 : {
87 : static_assert(
88 : std::is_nothrow_move_constructible<U>::value,
89 : "U must be nothrow move constructible for SBO");
90 5 : U* p = static_cast<U*>(src->ptr_);
91 5 : dst->ptr_ = ::new(static_cast<void*>(&dst->buffer_)) U(std::move(*p));
92 5 : dst->destroy_ = src->destroy_;
93 5 : dst->relocate_ = src->relocate_;
94 5 : p->~U();
95 5 : src->ptr_ = nullptr;
96 5 : src->destroy_ = nullptr;
97 5 : src->relocate_ = nullptr;
98 5 : }
99 :
100 : template<class U>
101 5 : static void relocate_large(
102 : small_unique_ptr* src,
103 : small_unique_ptr* dst)
104 : {
105 5 : dst->ptr_ = src->ptr_;
106 5 : dst->destroy_ = src->destroy_;
107 5 : dst->relocate_ = src->relocate_;
108 5 : src->ptr_ = nullptr;
109 5 : src->destroy_ = nullptr;
110 5 : src->relocate_ = nullptr;
111 5 : }
112 :
113 13 : void clear() noexcept
114 : {
115 13 : ptr_ = nullptr;
116 13 : destroy_ = nullptr;
117 13 : relocate_ = nullptr;
118 13 : }
119 :
120 : public:
121 : /** The type of the managed object.
122 : */
123 : typedef T element_type;
124 :
125 : /** The pointer type.
126 : */
127 : typedef T* pointer;
128 :
129 : /** Default constructor.
130 :
131 : Constructs an empty smart pointer that owns nothing.
132 :
133 : @post `get() == nullptr`
134 : */
135 13 : small_unique_ptr() noexcept
136 13 : : ptr_(nullptr)
137 13 : , destroy_(nullptr)
138 13 : , relocate_(nullptr)
139 : {
140 13 : }
141 :
142 : /** Construct from nullptr.
143 :
144 : Constructs an empty smart pointer that owns nothing.
145 :
146 : @post `get() == nullptr`
147 : */
148 1 : small_unique_ptr(std::nullptr_t) noexcept
149 1 : : small_unique_ptr()
150 : {
151 1 : }
152 :
153 : /** Construct from a raw pointer.
154 :
155 : Takes ownership of a heap-allocated object. The
156 : pointer must have been allocated with `new` and
157 : will be deleted when this smart pointer is destroyed.
158 :
159 : @note This constructor always uses the heap path.
160 : Use @ref emplace or @ref make_small_unique to
161 : benefit from small buffer optimization.
162 :
163 : @param p A pointer to a heap-allocated object,
164 : or `nullptr`.
165 :
166 : @tparam U The dynamic type. Must be convertible to `T*`.
167 :
168 : @post `get() == p`
169 : */
170 : template<class U
171 : ,class = typename std::enable_if<
172 : std::is_convertible<U*, T*>::value>::type>
173 : explicit
174 3 : small_unique_ptr(
175 : U* p) noexcept
176 3 : : ptr_(p)
177 3 : , destroy_(p ? &destroy_large<U> : nullptr)
178 3 : , relocate_(p ? &relocate_large<U> : nullptr)
179 : {
180 3 : }
181 :
182 : /** Copy constructor (deleted).
183 :
184 : small_unique_ptr is move-only.
185 : */
186 : small_unique_ptr(small_unique_ptr const&) = delete;
187 :
188 : /** Copy assignment (deleted).
189 :
190 : small_unique_ptr is move-only.
191 : */
192 : small_unique_ptr& operator=(small_unique_ptr const&) = delete;
193 :
194 : /** Move constructor.
195 :
196 : Transfers ownership from `other` to `*this`.
197 : For SBO objects, the managed object is move-constructed
198 : into this instance's buffer. For heap objects, the
199 : pointer is transferred.
200 :
201 : @param other The source smart pointer. Will be empty
202 : after this call.
203 :
204 : @post `other.get() == nullptr`
205 : */
206 5 : small_unique_ptr(small_unique_ptr&& other) noexcept
207 5 : : ptr_(nullptr)
208 5 : , destroy_(nullptr)
209 5 : , relocate_(nullptr)
210 : {
211 5 : if(other.ptr_)
212 4 : other.relocate_(&other, this);
213 5 : }
214 :
215 : /** Move assignment.
216 :
217 : Destroys any currently managed object, then transfers
218 : ownership from `other` to `*this`.
219 :
220 : @param other The source smart pointer. Will be empty
221 : after this call.
222 :
223 : @return `*this`
224 :
225 : @post `other.get() == nullptr`
226 : */
227 7 : small_unique_ptr& operator=(small_unique_ptr&& other) noexcept
228 : {
229 7 : if(this != &other)
230 : {
231 6 : reset();
232 6 : if(other.ptr_)
233 6 : other.relocate_(&other, this);
234 : }
235 7 : return *this;
236 : }
237 :
238 : /** Assign nullptr.
239 :
240 : Destroys any currently managed object and resets
241 : to empty state.
242 :
243 : @return `*this`
244 :
245 : @post `get() == nullptr`
246 : */
247 1 : small_unique_ptr& operator=(std::nullptr_t) noexcept
248 : {
249 1 : reset();
250 1 : return *this;
251 : }
252 :
253 : /** Destructor.
254 :
255 : Destroys the managed object if one exists. SBO
256 : objects are destroyed in-place; heap objects
257 : are deleted.
258 : */
259 21 : ~small_unique_ptr() noexcept
260 : {
261 21 : reset();
262 21 : }
263 :
264 : /** Destroy the managed object.
265 :
266 : If a managed object exists, it is destroyed (either
267 : in-place for SBO or deleted for heap). After this
268 : call, the smart pointer is empty.
269 :
270 : @post `get() == nullptr`
271 : */
272 29 : void reset() noexcept
273 : {
274 29 : if(ptr_)
275 : {
276 12 : destroy_(this);
277 12 : clear();
278 : }
279 29 : }
280 :
281 : /** Release ownership.
282 :
283 : Returns the managed pointer and relinquishes
284 : ownership without destroying the object. The
285 : caller is responsible for deleting the returned
286 : pointer.
287 :
288 : @warning For SBO objects, the returned pointer
289 : points to memory inside this object's buffer,
290 : which becomes invalid when this object is
291 : destroyed or reused.
292 :
293 : @return The previously managed pointer, or
294 : `nullptr` if empty.
295 :
296 : @post `get() == nullptr`
297 : */
298 1 : pointer release() noexcept
299 : {
300 1 : pointer p = ptr_;
301 1 : clear();
302 1 : return p;
303 : }
304 :
305 : /** Get the managed pointer.
306 :
307 : @return The stored pointer, or `nullptr` if empty.
308 : */
309 1 : pointer get() const noexcept
310 : {
311 1 : return ptr_;
312 : }
313 :
314 : /** Check if non-empty.
315 :
316 : @return `true` if this manages an object,
317 : `false` otherwise.
318 : */
319 19 : explicit operator bool() const noexcept
320 : {
321 19 : return ptr_ != nullptr;
322 : }
323 :
324 : /** Dereference the managed object.
325 :
326 : @return A reference to the managed object.
327 :
328 : @pre `get() != nullptr`
329 : */
330 : typename std::add_lvalue_reference<T>::type
331 1 : operator*() const noexcept
332 : {
333 1 : return *ptr_;
334 : }
335 :
336 : /** Member access.
337 :
338 : @return The stored pointer.
339 :
340 : @pre `get() != nullptr`
341 : */
342 12 : pointer operator->() const noexcept
343 : {
344 12 : return ptr_;
345 : }
346 :
347 : /** Swap with another small_unique_ptr.
348 :
349 : Exchanges the managed objects between `*this`
350 : and `other`.
351 :
352 : @param other The smart pointer to swap with.
353 : */
354 2 : void swap(small_unique_ptr& other) noexcept
355 : {
356 2 : small_unique_ptr tmp(std::move(other));
357 2 : other = std::move(*this);
358 2 : *this = std::move(tmp);
359 2 : }
360 :
361 : /** Construct a managed object in-place.
362 :
363 : Creates an object of type `U` with the given
364 : arguments. If `U` fits in the buffer and is
365 : nothrow move constructible, SBO is used;
366 : otherwise the object is heap-allocated.
367 :
368 : @param args Constructor arguments for `U`.
369 :
370 : @tparam U The concrete type to construct.
371 : Must be convertible to `T*`.
372 :
373 : @tparam Args Constructor argument types.
374 :
375 : @return A small_unique_ptr managing the new object.
376 :
377 : @throws Any exception thrown by `U`'s constructor.
378 : For heap allocation, may also throw `std::bad_alloc`.
379 : */
380 : // SBO path
381 : template<class U, class... Args>
382 : static
383 : typename std::enable_if<
384 : fits_in_buffer<U>::value &&
385 : std::is_convertible<U*, T*>::value,
386 : small_unique_ptr>::type
387 6 : emplace(Args&&... args)
388 : {
389 : static_assert(
390 : std::is_nothrow_move_constructible<U>::value,
391 : "U must be nothrow move constructible for SBO");
392 6 : small_unique_ptr p;
393 12 : p.ptr_ = ::new(static_cast<void*>(&p.buffer_)) U(
394 6 : std::forward<Args>(args)...);
395 6 : p.destroy_ = &destroy_small<U>;
396 6 : p.relocate_ = &relocate_small<U>;
397 6 : return p;
398 : }
399 :
400 : /** Construct a managed object in-place (heap path).
401 :
402 : Creates an object of type `U` on the heap when
403 : `U` does not fit in the buffer or does not meet
404 : SBO requirements.
405 :
406 : @param args Constructor arguments for `U`.
407 :
408 : @tparam U The concrete type to construct.
409 : Must be convertible to `T*`.
410 :
411 : @tparam Args Constructor argument types.
412 :
413 : @return A small_unique_ptr managing the new object.
414 :
415 : @throws Any exception thrown by `U`'s constructor,
416 : or `std::bad_alloc` on allocation failure.
417 : */
418 : // Heap path
419 : template<class U, class... Args>
420 : static
421 : typename std::enable_if<
422 : !fits_in_buffer<U>::value &&
423 : std::is_convertible<U*, T*>::value,
424 : small_unique_ptr>::type
425 4 : emplace(Args&&... args)
426 : {
427 4 : small_unique_ptr p;
428 4 : p.ptr_ = new U(std::forward<Args>(args)...);
429 4 : p.destroy_ = &destroy_large<U>;
430 4 : p.relocate_ = &relocate_large<U>;
431 4 : return p;
432 0 : }
433 : };
434 :
435 : /** Swap two small_unique_ptr objects.
436 :
437 : Exchanges the managed objects between `lhs` and `rhs`.
438 :
439 : @param lhs The first smart pointer.
440 : @param rhs The second smart pointer.
441 :
442 : @see small_unique_ptr::swap
443 : */
444 : template<class T, std::size_t N>
445 2 : void swap(
446 : small_unique_ptr<T, N>& lhs,
447 : small_unique_ptr<T, N>& rhs) noexcept
448 : {
449 2 : lhs.swap(rhs);
450 2 : }
451 :
452 : /** Create a small_unique_ptr with in-place construction.
453 :
454 : Constructs an object of type `U` managed by a
455 : small_unique_ptr. Uses small buffer optimization
456 : if `U` fits within `N` bytes and is nothrow move
457 : constructible; otherwise heap-allocates.
458 :
459 : @param args Constructor arguments forwarded to `U`.
460 :
461 : @tparam T The base type for the smart pointer.
462 :
463 : @tparam N The buffer size in bytes.
464 :
465 : @tparam U The concrete type to construct.
466 : Must be convertible to `T*`.
467 :
468 : @tparam Args Constructor argument types.
469 :
470 : @return A small_unique_ptr<T, N> managing the new object.
471 :
472 : @throws Any exception thrown by `U`'s constructor.
473 : For heap allocation, may also throw `std::bad_alloc`.
474 :
475 : @par Example
476 : @code
477 : struct Base { virtual ~Base() = default; };
478 : struct Derived : Base { int value; Derived(int v) : value(v) {} };
479 :
480 : auto p = make_small_unique<Base, 64, Derived>(42);
481 : @endcode
482 :
483 : @see small_unique_ptr, small_unique_ptr::emplace
484 : */
485 : template<class T, std::size_t N, class U, class... Args>
486 : typename std::enable_if<
487 : std::is_convertible<U*, T*>::value,
488 : small_unique_ptr<T, N>>::type
489 10 : make_small_unique(Args&&... args)
490 : {
491 : return small_unique_ptr<T, N>::template emplace<U>(
492 10 : std::forward<Args>(args)...);
493 : }
494 :
495 : } // capy
496 : } // boost
497 :
498 : #endif
|