tlx
counting_ptr.hpp
Go to the documentation of this file.
1/*******************************************************************************
2 * tlx/counting_ptr.hpp
3 *
4 * Part of tlx - http://panthema.net/tlx
5 *
6 * Copyright (C) 2013-2017 Timo Bingmann <tb@panthema.net>
7 *
8 * All rights reserved. Published under the Boost Software License, Version 1.0
9 ******************************************************************************/
10
11#ifndef TLX_COUNTING_PTR_HEADER
12#define TLX_COUNTING_PTR_HEADER
13
14#include <algorithm>
15#include <atomic>
16#include <cassert>
17#include <iosfwd>
18#include <type_traits>
19#include <utility>
20
21namespace tlx {
22
23//! default deleter for CountingPtr
25{
26public:
27 template <typename Type>
28 void operator () (Type* ptr) const noexcept {
29 delete ptr;
30 }
31};
32
33//! dummy deleter for CountingPtr
35{
36public:
37 template <typename Type>
38 void operator () (Type*) const noexcept { }
39};
40
41/*!
42 * High-performance smart pointer used as a wrapping reference counting pointer.
43 *
44 * This smart pointer class requires two functions in the template type: void
45 * inc_reference() and void dec_reference(). These must increment and decrement
46 * a reference count inside the templated object. When initialized, the type
47 * must have reference count zero. Each new object referencing the data calls
48 * inc_reference() and each destroying holder calls del_reference(). When the
49 * data object determines that it's internal count is zero, then it must destroy
50 * itself.
51 *
52 * Accompanying the CountingPtr is a class ReferenceCounter, from which
53 * reference counted classes may be derive from. The class ReferenceCounter
54 * implement all methods required for reference counting.
55 *
56 * The whole method is more similar to boost's instrusive_ptr, but also yields
57 * something resembling std::shared_ptr. However, compared to std::shared_ptr,
58 * this class only contains a single pointer, while shared_ptr contains two
59 * which are only related if constructed with std::make_shared.
60 *
61 * Another advantage with this method is that no kludges like
62 * std::enable_shared_from_this are needed.
63 */
64template <typename Type, typename Deleter = CountingPtrDefaultDeleter>
66{
67public:
68 //! contained type.
69 using element_type = Type;
70
71private:
72 //! the pointer to the currently referenced object.
73 Type* ptr_;
74
75 //! increment reference count of object.
76 void inc_reference(Type* o) noexcept
77 { if (o) o->inc_reference(); }
78
79 //! decrement reference count of current object and maybe delete it.
80 void dec_reference() noexcept {
81 if (ptr_ && ptr_->dec_reference())
82 Deleter()(ptr_);
83 }
84
85public:
86 //! all CountingPtr are friends such that they may steal pointers.
87 template <typename Other, typename OtherDeleter>
88 friend class CountingPtr;
89
90 //! \name Construction, Assignment and Destruction
91 //! \{
92
93 //! default constructor: contains a nullptr pointer.
94 CountingPtr() noexcept
95 : ptr_(nullptr) { }
96
97 //! implicit conversion from nullptr_t: contains a nullptr pointer.
98 CountingPtr(std::nullptr_t) noexcept // NOLINT
99 : ptr_(nullptr) { }
100
101 //! constructor from pointer: initializes new reference to ptr.
102 explicit CountingPtr(Type* ptr) noexcept
103 : ptr_(ptr)
104 { inc_reference(ptr_); }
105
106 //! copy-constructor: also initializes new reference to ptr.
107 CountingPtr(const CountingPtr& other) noexcept
108 : ptr_(other.ptr_)
109 { inc_reference(ptr_); }
110
111 //! copy-constructor: also initializes new reference to ptr.
112 template <typename Subclass,
113 typename = typename std::enable_if<
114 std::is_convertible<Subclass*, Type*>::value, void>::type>
116 : ptr_(other.ptr_)
117 { inc_reference(ptr_); }
118
119 //! move-constructor: just moves pointer, does not change reference counts.
120 CountingPtr(CountingPtr&& other) noexcept
121 : ptr_(other.ptr_)
122 { other.ptr_ = nullptr; }
123
124 //! move-constructor: just moves pointer, does not change reference counts.
125 template <typename Subclass,
126 typename = typename std::enable_if<
127 std::is_convertible<Subclass*, Type*>::value, void>::type>
129 : ptr_(other.ptr_)
130 { other.ptr_ = nullptr; }
131
132 //! copy-assignment operator: acquire reference on new one and dereference
133 //! current object.
134 CountingPtr& operator = (const CountingPtr& other) noexcept {
135 if (ptr_ == other.ptr_)
136 return *this;
137 inc_reference(other.ptr_);
139 ptr_ = other.ptr_;
140 return *this;
141 }
142
143 //! copy-assignment operator: acquire reference on new one and dereference
144 //! current object.
145 template <typename Subclass,
146 typename = typename std::enable_if<
147 std::is_convertible<Subclass*, Type*>::value, void>::type>
150 if (ptr_ == other.ptr_)
151 return *this;
152 inc_reference(other.ptr_);
154 ptr_ = other.ptr_;
155 return *this;
156 }
157
158 //! move-assignment operator: move reference of other to current object.
160 if (ptr_ == other.ptr_)
161 return *this;
163 ptr_ = other.ptr_;
164 other.ptr_ = nullptr;
165 return *this;
166 }
167
168 //! move-assignment operator: move reference of other to current object.
169 template <typename Subclass,
170 typename = typename std::enable_if<
171 std::is_convertible<Subclass*, Type*>::value, void>::type>
173 if (ptr_ == other.ptr_)
174 return *this;
176 ptr_ = other.ptr_;
177 other.ptr_ = nullptr;
178 return *this;
179 }
180
181 //! destructor: decrements reference count in ptr.
183
184 //! \}
185
186 //! \name Observers
187 //! \{
188
189 //! return the enclosed object as reference.
190 Type& operator * () const noexcept {
191 assert(ptr_);
192 return *ptr_;
193 }
194
195 //! return the enclosed pointer.
196 Type* operator -> () const noexcept {
197 assert(ptr_);
198 return ptr_;
199 }
200
201 //! return the enclosed pointer.
202 Type * get() const noexcept { return ptr_; }
203
204 //! test for a non-nullptr pointer
205 bool valid() const noexcept
206 { return (ptr_ != nullptr); }
207
208 //! cast to bool checks for a nullptr pointer
209 operator bool () const noexcept
210 { return valid(); }
211
212 //! test for a nullptr pointer
213 bool empty() const noexcept
214 { return (ptr_ == nullptr); }
215
216 //! if the object is referred by this CountingPtr only
217 bool unique() const noexcept
218 { return ptr_ && ptr_->unique(); }
219
220 //! Returns the number of different shared_ptr instances managing the
221 //! current object.
222 size_t use_count() const noexcept
223 { return ptr_->reference_count(); }
224
225 //! \}
226
227 //! \name Modifiers
228 //! \{
229
230 //! release contained pointer, frees object if this is the last reference.
231 void reset() {
233 ptr_ = nullptr;
234 }
235
236 //! swap enclosed object with another counting pointer (no reference counts
237 //! need change)
238 void swap(CountingPtr& b) noexcept
239 { std::swap(ptr_, b.ptr_); }
240
241 //! make and refer a copy if the original object was shared.
242 void unify() {
243 if (ptr_ && !ptr_->unique())
244 operator = (CountingPtr(new Type(*ptr_)));
245 }
246
247 //! \}
248
249 //! \name Comparison Operators
250 //! \{
251
252 //! test equality of only the pointer values.
253 bool operator == (const CountingPtr& other) const noexcept
254 { return ptr_ == other.ptr_; }
255
256 //! test inequality of only the pointer values.
257 bool operator != (const CountingPtr& other) const noexcept
258 { return ptr_ != other.ptr_; }
259
260 //! test equality of only the address pointed to
261 bool operator == (Type* other) const noexcept
262 { return ptr_ == other; }
263
264 //! test inequality of only the address pointed to
265 bool operator != (Type* other) const noexcept
266 { return ptr_ != other; }
267
268 //! compare the pointer values.
269 bool operator < (const CountingPtr& other) const noexcept
270 { return ptr_ < other.ptr_; }
271
272 //! compare the pointer values.
273 bool operator <= (const CountingPtr& other) const noexcept
274 { return ptr_ <= other.ptr_; }
275
276 //! compare the pointer values.
277 bool operator > (const CountingPtr& other) const noexcept
278 { return ptr_ > other.ptr_; }
279
280 //! compare the pointer values.
281 bool operator >= (const CountingPtr& other) const noexcept
282 { return ptr_ >= other.ptr_; }
283
284 //! compare the pointer values.
285 bool operator < (Type* other) const noexcept
286 { return ptr_ < other; }
287
288 //! compare the pointer values.
289 bool operator <= (Type* other) const noexcept
290 { return ptr_ <= other; }
291
292 //! compare the pointer values.
293 bool operator > (Type* other) const noexcept
294 { return ptr_ > other; }
295
296 //! compare the pointer values.
297 bool operator >= (Type* other) const noexcept
298 { return ptr_ >= other; }
299
300 //! \}
301};
302
303//! make alias due to similarity with std::shared_ptr<T>
304template <typename Type>
306
307//! make alias for dummy deleter
308template <typename Type>
310
311//! method analogous to std::make_shared and std::make_unique.
312template <typename Type, typename... Args>
314 return CountingPtr<Type>(new Type(std::forward<Args>(args) ...));
315}
316
317//! swap enclosed object with another counting pointer (no reference counts need
318//! change)
319template <typename A, typename D>
320void swap(CountingPtr<A, D>& a1, CountingPtr<A, D>& a2) noexcept {
321 a1.swap(a2);
322}
323
324//! print pointer
325template <typename A, typename D>
326std::ostream& operator << (std::ostream& os, const CountingPtr<A, D>& c) {
327 return os << c.get();
328}
329
330/*!
331 * Provides reference counting abilities for use with CountingPtr.
332 *
333 * Use as superclass of the actual object, this adds a reference_count_
334 * value. Then either use CountingPtr as pointer to manage references and
335 * deletion, or just do normal new and delete.
336 */
338{
339private:
340 //! the reference count is kept mutable for CountingPtr<const Type> to
341 //! change the reference count.
342 mutable std::atomic<size_t> reference_count_;
343
344public:
345 //! new objects have zero reference count
347 : reference_count_(0) { }
348
349 //! coping still creates a new object with zero reference count
351 : reference_count_(0) { }
352
353 //! assignment operator, leaves pointers unchanged
355 // changing the contents leaves pointers unchanged
356 return *this;
357 }
358
360 { assert(reference_count_ == 0); }
361
362public:
363 //! Call whenever setting a pointer to the object.
364 void inc_reference() const noexcept
365 { ++reference_count_; }
366
367 /*!
368 * Call whenever resetting (i.e. overwriting) a pointer to the object.
369 * IMPORTANT: In case of self-assignment, call AFTER inc_reference().
370 *
371 * \return if the object has to be deleted (i.e. if it's reference count
372 * dropped to zero)
373 */
374 bool dec_reference() const noexcept {
375 assert(reference_count_ > 0);
376 return (--reference_count_ == 0);
377 }
378
379 //! Test if the ReferenceCounter is referenced by only one CountingPtr.
380 bool unique() const noexcept
381 { return (reference_count_ == 1); }
382
383 //! Return the number of references to this object (for debugging)
384 size_t reference_count() const noexcept
385 { return reference_count_; }
386};
387
388//! make alias due to CountingPtr's similarity with std::shared_ptr<T>
390
391/** \page tlx_counting_ptr CountingPtr – an intrusive reference counting pointer
392
393\brief \ref CountingPtr is an implementation of <b>intrusive reference counting</b>.
394This is similar, but not identical to boost or C++ TR1's \c
395shared_ptr. Intrusive reference counting requires the counted class to contain
396the counter, which is not required by <tt>std::shared_ptr</tt>.
397
398Intrusive counting is often faster due to fewer cache faults. Furthermore, \ref
399CountingPtr is a <b>single pointer</b>, whereas <tt>std::shared_ptr</tt>
400actually contains (at least) two pointers. \ref CountingPtr also creates a lot
401less debug info due to reduced complexity.
402
403\ref CountingPtr is accompanied by \ref ReferenceCounter, which contains the
404actual reference counter (a single integer). A reference counted object must
405derive from \ref ReferenceCounter :
406
407\code
408struct Something : public tlx::ReferenceCounter
409{
410};
411\endcode
412
413Code that now wishes to use pointers referencing this object, will typedef an
414\ref CountingPtr, which is used to increment and decrement the included
415reference counter automatically.
416
417\code
418using SomethingPtr = tlx::CountingPtr<Something>;
419{
420 // create new instance of something
421 SomethingPtr p1 = new something;
422 {
423 // create a new reference to the same instance (no deep copy!)
424 SomethingPtr p2 = p1;
425 // this block end will decrement the reference count, but not delete the object
426 }
427 // this block end will delete the object
428}
429\endcode
430
431The \ref CountingPtr can generally be used like a usual pointer or \c
432std::shared_ptr (see the docs for more).
433
434*/
435
436} // namespace tlx
437
438#endif // !TLX_COUNTING_PTR_HEADER
439
440/******************************************************************************/
default deleter for CountingPtr
void operator()(Type *ptr) const noexcept
dummy deleter for CountingPtr
void operator()(Type *) const noexcept
High-performance smart pointer used as a wrapping reference counting pointer.
void inc_reference(Type *o) noexcept
increment reference count of object.
bool unique() const noexcept
if the object is referred by this CountingPtr only
void unify()
make and refer a copy if the original object was shared.
bool empty() const noexcept
test for a nullptr pointer
CountingPtr(Type *ptr) noexcept
constructor from pointer: initializes new reference to ptr.
Type * get() const noexcept
return the enclosed pointer.
CountingPtr(const CountingPtr &other) noexcept
copy-constructor: also initializes new reference to ptr.
~CountingPtr()
destructor: decrements reference count in ptr.
void swap(CountingPtr &b) noexcept
swap enclosed object with another counting pointer (no reference counts need change)
friend class CountingPtr
all CountingPtr are friends such that they may steal pointers.
Type * operator->() const noexcept
return the enclosed pointer.
bool operator==(const CountingPtr &other) const noexcept
test equality of only the pointer values.
bool operator<=(const CountingPtr &other) const noexcept
compare the pointer values.
CountingPtr(std::nullptr_t) noexcept
implicit conversion from nullptr_t: contains a nullptr pointer.
CountingPtr & operator=(const CountingPtr &other) noexcept
copy-assignment operator: acquire reference on new one and dereference current object.
void dec_reference() noexcept
decrement reference count of current object and maybe delete it.
Type & operator*() const noexcept
return the enclosed object as reference.
bool valid() const noexcept
test for a non-nullptr pointer
bool operator>(const CountingPtr &other) const noexcept
compare the pointer values.
Type * ptr_
the pointer to the currently referenced object.
Type element_type
contained type.
bool operator!=(const CountingPtr &other) const noexcept
test inequality of only the pointer values.
CountingPtr(CountingPtr &&other) noexcept
move-constructor: just moves pointer, does not change reference counts.
void reset()
release contained pointer, frees object if this is the last reference.
CountingPtr(const CountingPtr< Subclass, Deleter > &other) noexcept
copy-constructor: also initializes new reference to ptr.
CountingPtr(CountingPtr< Subclass, Deleter > &&other) noexcept
move-constructor: just moves pointer, does not change reference counts.
bool operator<(const CountingPtr &other) const noexcept
compare the pointer values.
CountingPtr() noexcept
default constructor: contains a nullptr pointer.
size_t use_count() const noexcept
Returns the number of different shared_ptr instances managing the current object.
bool operator>=(const CountingPtr &other) const noexcept
compare the pointer values.
Provides reference counting abilities for use with CountingPtr.
size_t reference_count() const noexcept
Return the number of references to this object (for debugging)
bool unique() const noexcept
Test if the ReferenceCounter is referenced by only one CountingPtr.
std::atomic< size_t > reference_count_
the reference count is kept mutable for CountingPtr<const Type> to change the reference count.
ReferenceCounter() noexcept
new objects have zero reference count
bool dec_reference() const noexcept
Call whenever resetting (i.e.
void inc_reference() const noexcept
Call whenever setting a pointer to the object.
ReferenceCounter(const ReferenceCounter &) noexcept
coping still creates a new object with zero reference count
ReferenceCounter & operator=(const ReferenceCounter &) noexcept
assignment operator, leaves pointers unchanged
void swap(CountingPtr< A, D > &a1, CountingPtr< A, D > &a2) noexcept
swap enclosed object with another counting pointer (no reference counts need change)
CountingPtr< Type > make_counting(Args &&... args)
method analogous to std::make_shared and std::make_unique.
std::ostream & operator<<(std::ostream &os, const CountingPtr< A, D > &c)
print pointer