Library of Assembled Shared Sources
smart_ptr_policies.h
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2024 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43/** @defgroup SmartPtr Smart Pointers
44 * @brief collection of smart pointers as ScopedPtr, SharedPtr, ...
45 * @author Bram de Greve [Bramz]
46 *
47 * @section Pointers
48 *
49 * - lass::util::ScopedPtr: a smart pointer for single ownership, deallocates on end of scope.
50 * - lass::util::SharedPtr: a smart pointer for shared ownership, keeping track of reference count.
51 * - lass::util::SmartI: a smart pointer to COM interfaces.
52 *
53 * @section StoragePolicy
54 *
55 * Storage polices control how the pointees are stored in the smart pointer and how they should
56 * be deallocated. Two default storage policies are provided for objects allocated by @c new
57 * and arrays allocated by @c new[], but it should be possible to write special storage policies
58 * to embed other @e smart pointers in our lass::util smart pointer classes.
59 *
60 * @subsection StorageConcept concept of storage policy
61 *
62 * All storage policies should implement the following interface. Some functions like
63 * @c at(size_t) should only be implemented for array-like pointees. So they can safely be
64 * ommited if pointee does not act like an array.
65 *
66 * @code
67 * template <typename T>
68 * class StoragePolicy
69 * {
70 * public:
71 *
72 * typedef ... TStorage; // the type of the storage_ object (usually T*)
73 * typedef ... TPointer; // return type of pointer() or operator-> (usually T*)
74 * typedef ... TReference; // return type of operator* or operator[] (usually T&)
75 *
76 * TStorage& storage();
77 * // return reference to storage object (usualy to a pointer)
78 *
79 * const TStorage& storage() const;
80 * // return const-reference to storage object
81 *
82 * protected:
83 *
84 * StoragePolicy();
85 * // should initialize with default storage object (in most cases: NULL)
86 *
87 * StoragePolicy(T* pointee);
88 * // should initialize to store given pointee.
89 *
90 * TPointer pointer() const;
91 * // should return a pointer to the pointee
92 *
93 * TReference at(size_t iIndex) const;
94 * // for arrays only: should return reference to iIndex'th object in array
95 * // (YOU CAN OMMIT THIS FOR NON-ARRAYS)
96 *
97 * void dispose();
98 * // deallocate the pointee (e.g. by a delete or delete [])
99 *
100 * bool isNull() const;
101 * // return true if storage contains equivalent of NULL pointer.
102 *
103 * void swap(StoragePolicy<T>& other);
104 * // swap storage object with other policy.
105 * };
106 * @endcode
107 *
108 * @subsection StorageModels implemenated models of storage policy
109 *
110 * - lass::util::ObjectStorage: simple storage policy for objects allocated by @c new.
111 * - lass::util::ArrayStorage: simple storage policy for arrays allocated by @c new[].
112 *
113 * @section CounterPolicy
114 *
115 * Counter policies control how reference counting should be done. In contrary to the storage
116 * policies, this is only used by the SharedPtr.
117 *
118 * @subsection CounterConcept concept of counter policy
119 *
120 * All counter policies should implement the following interface. The template parameter
121 * @c TStorage is of the same type as @c TStorage in the used storage policy. It will useually
122 * be a pointer to the pointee.
123 *
124 * @code
125 * class CounterPolicy
126 * {
127 * public:
128 *
129 * typedef ... TCount; // type of reference counter variable (should be an integer)
130 *
131 * protected:
132 *
133 * DefaultCounter(); // brings object in valid state without setting any counter.
134 *
135 * template <typename TStorage> void init(TStorage& pointee);
136 * // initialize reference count one one. This is called on acquring of object by first owner
137 * // (increment isn't called for first owner).
138 *
139 * template <typename TStorage> void dispose(TStorage& pointee);
140 * // clean up the counter (not the pointee). This is called when reference count drops to
141 * // zero (thus on release by last owner).
142 *
143 * template <typename TStorage> void increment(TStorage& pointee);
144 * // increment the reference count by one. This is called on acquiring of object by any owner
145 * // except the first (the first one calls init(TStorage&) instead).
146 *
147 * template <typename TStorage> bool decrement(TStorage& pointee);
148 * // decrement the reference count by one and returns true if it drops below one. This is
149 * // called on every release of the object by any owner. It should return true if @e after
150 * // the decrement the reference count turns out the be less than one (i.e. there are no more
151 * // owners, this was the last one), to indicate the pointee should be deallocated now. In
152 * // all other cases, it should return false @e after the (i.e. if the reference count is
153 * // still non-zero after the decrement).
154 *
155 * template <typename TStorage> TCount count(TStorage& pointee) const;
156 * // return the reference count, i.e. number of owners of the pointee
157 *
158 * void swap(DefaultCounter& other);
159 * // swap any reference counters with other policy.
160 * };
161 * @endcode
162 *
163 * @subsection CounterModels implemenated models of counter policy
164 *
165 * - lass::util::DefaultCounter: allocates extra counter on heap to keep track of reference count.
166 * - lass::util::IntrusiveCounter: uses a member of the object as reference count.
167 */
168
169#ifndef LASS_GUARDIAN_OF_INCLUSION_UTIL_SMART_PTR_POLICIES_H
170#define LASS_GUARDIAN_OF_INCLUSION_UTIL_SMART_PTR_POLICIES_H
171
172#include "util_common.h"
173#include "atomic.h"
174#include "../meta/empty_type.h"
175
176namespace lass
177{
178namespace util
179{
180
181// --- storage policies ----------------------------------------------------------------------------
182
183/** @class ObjectStorage
184 * @ingroup SmartPtr
185 * @brief Default storage policy for single objects, implementation of StoragePolicy concept
186 * @author Bram de Greve [Bramz]
187 *
188 * This policy is meant for simple pointees that are allocated in @c C++ by @c new or equivalent
189 * (i.e. stuff that should be deallocated by @c delete ). The storage type @c TStorage used for
190 * this policy simply is an ordinary pointer to that pointee (@c T*)/.
191 */
192template
193<
194 typename T,
195 typename Cascade = meta::EmptyType
196>
197class ObjectStorage: public Cascade
198{
199public:
200
201 typedef ObjectStorage<T, Cascade> TSelf;
202 typedef T* TStorage;
203 typedef T* TPointer;
204 typedef T& TReference;
205
206 TStorage& storage() { return storage_; }
207 const TStorage& storage() const { return storage_; }
208
209protected:
210
211 ObjectStorage(): Cascade(), storage_(defaultStorage()) {}
212 explicit ObjectStorage(T* pointee): Cascade(), storage_(pointee) {}
213 ObjectStorage(const TSelf& other): Cascade(other), storage_(other.storage_) {}
214 template <typename U> ObjectStorage(const ObjectStorage<U, Cascade>& other): Cascade(other), storage_(other.storage()) {}
215 ObjectStorage(TSelf&& other) noexcept:
216 Cascade(std::forward<Cascade>(other)),
217 storage_(other.storage_)
218 {
219 other.storage_ = nullptr;
220 }
221
222 TPointer pointer() const { return storage_; }
223
224 void dispose()
225 {
226 delete storage_;
227 storage_ = 0;
228 }
229
230 bool isNull() const { return !storage_; }
231 void swap(TSelf& other) { Cascade::swap(other); std::swap(storage_, other.storage_); }
232
233 template <typename U> const ObjectStorage<U, Cascade> staticCast() const
234 {
235 return ObjectStorage<U, Cascade>(static_cast<U*>(storage_), *this);
236 }
237
238 template <typename U> const ObjectStorage<U, Cascade> dynamicCast() const
239 {
240 if (U* p = dynamic_cast<U*>(storage_))
241 {
242 return ObjectStorage<U, Cascade>(p, *this);
243 }
244 return ObjectStorage<U, Cascade>();
245 }
246
247 template <typename U> const ObjectStorage<U, Cascade> constCast() const
248 {
249 return ObjectStorage<U, Cascade>(const_cast<U*>(storage_), *this);
250 }
251
252 static TStorage defaultStorage() { return 0; }
253
254private:
255
256 template <typename U, typename C> friend class ObjectStorage;
257 template <typename U, template <typename, typename> class S, typename C> friend class SharedPtr;
258
259 ObjectStorage(T* pointee, const Cascade& cascade): Cascade(cascade), storage_(pointee) {}
260
261 TStorage storage_;
262};
263
264
265
266/** @class ArrayStorage
267 * @ingroup SmartPtr
268 * @brief Default storage policy for arrays, implementation of StoragePolicy concept
269 * @author Bram de Greve [Bramz]
270 *
271 * This policy is meant for simple arrays that are allocated in @c C++ by @c new[] or equivalent
272 * (i.e. stuff that should be deallocated by @c delete[] ). The storage type @c TStorage used for
273 * this policy simply is an ordinary pointer to that array (@c T*)/.
274 */
275template
276<
277 typename T,
278 typename Cascade = meta::EmptyType
279>
280class ArrayStorage: public Cascade
281{
282public:
283
284 typedef ArrayStorage<T, Cascade> TSelf;
285 typedef T* TStorage;
286 typedef T* TPointer;
287 typedef T& TReference;
288
289 TStorage& storage() { return storage_; }
290 const TStorage& storage() const { return storage_; }
291
292protected:
293
294 ArrayStorage(): Cascade(), storage_(defaultStorage()) { }
295 explicit ArrayStorage(T* pointee): Cascade(), storage_(pointee) { }
296 ArrayStorage(const TSelf& other): Cascade(other), storage_(other.storage_) {}
297 template <typename U> ArrayStorage(const ArrayStorage<U, Cascade>& other): Cascade(other), storage_(other.storage()) {}
298 ArrayStorage(TSelf&& other) noexcept:
299 Cascade(std::forward<Cascade>(other)),
300 storage_(other.storage_)
301 {
302 other.storage_ = nullptr;
303 }
304
305 TPointer pointer() const { return storage_; }
306 TReference at(size_t index) const { return storage_[index]; }
307
308 void dispose()
309 {
310 delete [] storage_;
311 storage_ = 0;
312 }
313
314 bool isNull() const { return !storage_; }
315 void swap(TSelf& other) { Cascade::swap(other); std::swap(storage_, other.storage_); }
316
317 static TStorage defaultStorage() { return 0; }
318
319private:
320
321 TStorage storage_;
322};
323
324
325
326
327
328
329// --- counter policies ----------------------------------------------------------------------------
330
331/** @class DefaultCounter
332 * @ingroup SmartPtr
333 * @brief The default counter for the shared pointers, implementation of CounterPolicy concept.
334 * @author Bram de Greve [Bramz]
335 *
336 * The default counter will use an self-owned integer as counter object. It will allocate this on
337 * the heap and share it between all smart pointers that own the same pointee (duh!). Of course
338 * it will be so nice to clean it up by itself too.
339 */
340class DefaultCounter
341{
342public:
343
344 typedef size_t TCount;
345
346protected:
347
348 DefaultCounter(): count_(nullptr) {}
349 DefaultCounter(const DefaultCounter& other): count_(other.count_) {}
350 DefaultCounter(DefaultCounter&& other) noexcept:
351 count_(other.count_)
352 {
353 other.count_ = nullptr;
354 }
355
356 template <typename TStorage> void init(TStorage& /*pointee*/)
357 {
358 count_ = initHeapCounter(1);
359 LASS_ASSERT(count_ && count_->load(std::memory_order_relaxed) == 1);
360 }
361
362 template <typename TStorage> void dispose(TStorage& /*pointee*/)
363 {
364 LASS_ASSERT(count_ && count_->load(std::memory_order_relaxed) == 0);
365 disposeHeapCounter(count_);
366 count_ = nullptr;
367 }
368
369 template <typename TStorage> void increment(TStorage& /*pointee*/)
370 {
371 LASS_ASSERT(count_);
372 [[maybe_unused]] const TCount oldCount = count_->fetch_add(1, std::memory_order_relaxed);
373 LASS_ASSERT(oldCount >= 1); // otherwise the object would not exist
374 }
375
376 template <typename TStorage> bool decrement(TStorage& /*pointee*/)
377 {
378 LASS_ASSERT(count_);
379 const TCount oldCount = count_->fetch_sub(1, std::memory_order_release);
380 LASS_ASSERT(oldCount > 0); // otherwise the object would have been dead already
381 if (oldCount == 1)
382 {
383 // we just decremented the last one, so current must be 0.
384 std::atomic_thread_fence(std::memory_order_acquire);
385 return true;
386 }
387 return false;
388 }
389
390 template <typename TStorage> TCount count(TStorage& /*pointee*/) const
391 {
392 LASS_ASSERT(count_);
393 const TCount count = count_->load(std::memory_order_relaxed);
394 LASS_ASSERT(count > 0);
395 return count;
396 }
397
398 void swap(DefaultCounter& other) { std::swap(count_, other.count_); }
399
400private:
401
402 typedef std::atomic<TCount> TAtomicCount;
403
404 LASS_DLL TAtomicCount* initHeapCounter(TCount initialValue);
405 LASS_DLL void disposeHeapCounter(TAtomicCount* counter);
406
407 TAtomicCount* count_;
408};
409
410
411
412
413/** @class IntrusiveCounter
414 * @brief implementation of CounterPolicy concept, using a counter in the managed object itself.
415 * @ingroup SmartPtr
416 * @author Bram de Greve [Bramz]
417 *
418 * This comes from "C++ Templates, The Complete Guide" by David Vandevoorde and
419 * Nicolai M. Josuttis. See their @c MemberReferenceCount policy.
420 *
421 * The DefaultCounter policy puts the reference counter outside the managed object. That's great in
422 * most cases, because that way, the managed type doesn't have to be designed to be used with our
423 * SharedPtr. However, in some cases it is more interesting to put the reference counter @e inside
424 * the managed object. You can use the IntrusiveCounter policy to use such a reference counter with
425 * our SharedPtr.
426 *
427 * @code
428 * struct Foo
429 * {
430 * std::string blablabla;
431 * int referenceCount;
432 * };
433 *
434 * typedef lass::util::SharedPtr
435 * <
436 * Foo,
437 * lass::util::ObjectStorage,
438 * lass::util::IntrusiveCounter
439 * <
440 * Foo, // the managed type (containing the counter)
441 * int, // type of the counter
442 * &Foo::referenceCount // pointer to the counter
443 * >
444 * >
445 * TFooPtr;
446 *
447 * TFooPtr foo(new Foo);
448 * @endcode
449 */
450template
451<
452 typename T,
453 typename CounterType,
454 CounterType T::* referenceCounter
455>
457
458
459template
460<
461 typename T,
462 typename CounterType,
463 volatile CounterType T::* referenceCounter
464>
465class
466[[deprecated("IntrusiveCounter with volatile count is deprecated, use it with std::atomic instead")]]
467IntrusiveCounter<T, volatile CounterType, referenceCounter>
468{
469public:
470
472 typedef CounterType TCount;
473
474protected:
475
476 template <typename TStorage> void init(TStorage& pointee)
477 {
478 LASS_ASSERT(pointee);
479 (pointee->*referenceCounter) = 1;
480 }
481
482 template <typename TStorage> void dispose(TStorage& LASS_UNUSED(pointee))
483 {
484 LASS_ASSERT(pointee && (pointee->*referenceCounter) == 0);
485 }
486
487 template <typename TStorage> void increment(TStorage& pointee)
488 {
489 LASS_ASSERT(pointee);
490 TCount oldCount = 0, newCount = 0;
491 do
492 {
493 oldCount = pointee->*referenceCounter;
494 LASS_ASSERT(oldCount > 0);
495 newCount = oldCount + 1;
496 }
497 while (!atomicCompareAndSwap(pointee->*referenceCounter, oldCount, newCount));
498 }
499
500 template <typename TStorage> bool decrement(TStorage& pointee)
501 {
502 LASS_ASSERT(pointee);
503 TCount oldCount = 0, newCount = 0;
504 do
505 {
506 oldCount = pointee->*referenceCounter;
507 LASS_ASSERT(oldCount > 0);
508 newCount = oldCount - 1;
509 }
510 while (!atomicCompareAndSwap(pointee->*referenceCounter, oldCount, newCount));
511 return newCount == 0;
512 }
513
514 template <typename TStorage> TCount count(TStorage& pointee) const
515 {
516 LASS_ASSERT(pointee && (pointee->*referenceCounter) > 0);
517 return pointee->*referenceCounter;
518 }
519
520 void swap(TSelf& /*other*/)
521 {
522 }
523};
524
525
526template
527<
528 typename T,
529 typename CounterType,
530 std::atomic<CounterType> T::* referenceCounter
531>
532class IntrusiveCounter<T, std::atomic<CounterType>, referenceCounter>
533{
534public:
535
536 typedef IntrusiveCounter<T, std::atomic<CounterType>, referenceCounter> TSelf;
537 typedef CounterType TCount;
538
539protected:
540
541 template <typename TStorage> void init(TStorage& pointee)
542 {
543 LASS_ASSERT(pointee);
544 (pointee->*referenceCounter).store(1, std::memory_order_relaxed);
545 }
546
547 template <typename TStorage> void dispose([[maybe_unused]] TStorage& pointee)
548 {
549 LASS_ASSERT(pointee && (pointee->*referenceCounter).load(std::memory_order_relaxed) == 0);
550 }
551
552 template <typename TStorage> void increment(TStorage& pointee)
553 {
554 LASS_ASSERT(pointee);
555 [[maybe_unused]] const TCount oldCount = (pointee->*referenceCounter).fetch_add(1, std::memory_order_relaxed);
556 LASS_ASSERT(oldCount >= 1); // otherwise the object would not exist
557 }
558
559 template <typename TStorage> bool decrement(TStorage& pointee)
560 {
561 LASS_ASSERT(pointee);
562 const TCount oldCount = (pointee->*referenceCounter).fetch_sub(1, std::memory_order_release);
563 LASS_ASSERT(oldCount > 0); // otherwise the object would have been dead already
564 if (oldCount == 1)
565 {
566 // we just decremented the last one, so current must be 0.
567 std::atomic_thread_fence(std::memory_order_acquire);
568 return true;
569 }
570 return false;
571 }
572
573 template <typename TStorage> TCount count(TStorage& pointee) const
574 {
575 LASS_ASSERT(pointee );
576 const TCount count = (pointee->*referenceCounter).load(std::memory_order_relaxed);
577 LASS_ASSERT(count > 0);
578 return count;
579 }
580
581 void swap(TSelf& /*other*/)
582 {
583 }
584};
585
586
587
588
589}
590
591}
592
593#endif
594
595// EOF
implementation of CounterPolicy concept, using a counter in the managed object itself.
bool atomicCompareAndSwap(volatile T &dest, T expectedValue, T newValue)
Performs the following pseudocode in an atomic way (no other threads can intervene):
Definition atomic.h:94
#define LASS_DLL
DLL interface: import or export symbols?
general utility, debug facilities, ...
Library for Assembled Shared Sources.
Definition config.h:53