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 
21 namespace tlx {
22 
23 //! default deleter for CountingPtr
25 {
26 public:
27  template <typename Type>
28  void operator () (Type* ptr) const noexcept {
29  delete ptr;
30  }
31 };
32 
33 //! dummy deleter for CountingPtr
35 {
36 public:
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  */
64 template <typename Type, typename Deleter = CountingPtrDefaultDeleter>
66 {
67 public:
68  //! contained type.
69  using element_type = Type;
70 
71 private:
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 
85 public:
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_);
138  dec_reference();
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>
148  CountingPtr&
149  operator = (const CountingPtr<Subclass, Deleter>& other) noexcept {
150  if (ptr_ == other.ptr_)
151  return *this;
152  inc_reference(other.ptr_);
153  dec_reference();
154  ptr_ = other.ptr_;
155  return *this;
156  }
157 
158  //! move-assignment operator: move reference of other to current object.
159  CountingPtr& operator = (CountingPtr&& other) noexcept {
160  if (ptr_ == other.ptr_)
161  return *this;
162  dec_reference();
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>
172  CountingPtr& operator = (CountingPtr<Subclass, Deleter>&& other) noexcept {
173  if (ptr_ == other.ptr_)
174  return *this;
175  dec_reference();
176  ptr_ = other.ptr_;
177  other.ptr_ = nullptr;
178  return *this;
179  }
180 
181  //! destructor: decrements reference count in ptr.
182  ~CountingPtr() { dec_reference(); }
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() {
232  dec_reference();
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>
304 template <typename Type>
306 
307 //! make alias for dummy deleter
308 template <typename Type>
310 
311 //! method analogous to std::make_shared and std::make_unique.
312 template <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)
319 template <typename A, typename D>
320 void swap(CountingPtr<A, D>& a1, CountingPtr<A, D>& a2) noexcept {
321  a1.swap(a2);
322 }
323 
324 //! print pointer
325 template <typename A, typename D>
326 std::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 {
339 private:
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 
344 public:
345  //! new objects have zero reference count
346  ReferenceCounter() noexcept
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
354  ReferenceCounter& operator = (const ReferenceCounter&) noexcept {
355  // changing the contents leaves pointers unchanged
356  return *this;
357  }
358 
360  { assert(reference_count_ == 0); }
361 
362 public:
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>.
394 This is similar, but not identical to boost or C++ TR1's \c
395 shared_ptr. Intrusive reference counting requires the counted class to contain
396 the counter, which is not required by <tt>std::shared_ptr</tt>.
397 
398 Intrusive counting is often faster due to fewer cache faults. Furthermore, \ref
399 CountingPtr is a <b>single pointer</b>, whereas <tt>std::shared_ptr</tt>
400 actually contains (at least) two pointers. \ref CountingPtr also creates a lot
401 less debug info due to reduced complexity.
402 
403 \ref CountingPtr is accompanied by \ref ReferenceCounter, which contains the
404 actual reference counter (a single integer). A reference counted object must
405 derive from \ref ReferenceCounter :
406 
407 \code
408 struct Something : public tlx::ReferenceCounter
409 {
410 };
411 \endcode
412 
413 Code that now wishes to use pointers referencing this object, will typedef an
414 \ref CountingPtr, which is used to increment and decrement the included
415 reference counter automatically.
416 
417 \code
418 using 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 
431 The \ref CountingPtr can generally be used like a usual pointer or \c
432 std::shared_ptr (see the docs for more).
433 
434 */
435 
436 } // namespace tlx
437 
438 #endif // !TLX_COUNTING_PTR_HEADER
439 
440 /******************************************************************************/
size_t use_count() const noexcept
Returns the number of different shared_ptr instances managing the current object. ...
void unify()
make and refer a copy if the original object was shared.
CountingPtr< Type > make_counting(Args &&...args)
method analogous to std::make_shared and std::make_unique.
Type element_type
contained type.
CountingPtr(Type *ptr) noexcept
constructor from pointer: initializes new reference to ptr.
bool unique() const noexcept
Test if the ReferenceCounter is referenced by only one CountingPtr.
dummy deleter for CountingPtr
static bool operator!=(const StringView &a, const std::string &b) noexcept
inequality operator to compare a StringView with a std::string
void operator()(Type *ptr) const noexcept
CountingPtr(const CountingPtr< Subclass, Deleter > &other) noexcept
copy-constructor: also initializes new reference to ptr.
static bool operator>(const StringView &x, const std::string &y) noexcept
void reset()
release contained pointer, frees object if this is the last reference.
default deleter for CountingPtr
bool valid() const noexcept
test for a non-nullptr pointer
ReferenceCounter(const ReferenceCounter &) noexcept
coping still creates a new object with zero reference count
Type * ptr_
the pointer to the currently referenced object.
CountingPtr() noexcept
default constructor: contains a nullptr pointer.
CountingPtr(CountingPtr &&other) noexcept
move-constructor: just moves pointer, does not change reference counts.
CountingPtr(const CountingPtr &other) noexcept
copy-constructor: also initializes new reference to ptr.
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.
static bool operator>=(const StringView &x, const std::string &y) noexcept
static bool operator<=(const StringView &x, const std::string &y) noexcept
void swap(CountingPtr< A, D > &a1, CountingPtr< A, D > &a2) noexcept
swap enclosed object with another counting pointer (no reference counts need change) ...
size_t reference_count() const noexcept
Return the number of references to this object (for debugging)
bool unique() const noexcept
if the object is referred by this CountingPtr only
void inc_reference(Type *o) noexcept
increment reference count of object.
High-performance smart pointer used as a wrapping reference counting pointer.
static bool operator==(const StringView &a, const std::string &b) noexcept
equality operator to compare a StringView with a std::string
bool empty() const noexcept
test for a nullptr pointer
~CountingPtr()
destructor: decrements reference count in ptr.
void inc_reference() const noexcept
Call whenever setting a pointer to the object.
void dec_reference() noexcept
decrement reference count of current object and maybe delete it.
CountingPtr(CountingPtr< Subclass, Deleter > &&other) noexcept
move-constructor: just moves pointer, does not change reference counts.
static bool operator<(const StringView &a, const std::string &b) noexcept
less operator to compare a StringView with a std::string lexicographically
CountingPtr(std::nullptr_t) noexcept
implicit conversion from nullptr_t: contains a nullptr pointer.
Provides reference counting abilities for use with CountingPtr.
void swap(CountingPtr &b) noexcept
swap enclosed object with another counting pointer (no reference counts need change) ...