/[svn]/linuxsampler/trunk/src/common/Ref.h
ViewVC logotype

Contents of /linuxsampler/trunk/src/common/Ref.h

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3768 - (show annotations) (download) (as text)
Fri May 15 19:52:31 2020 UTC (3 years, 11 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 17399 byte(s)
Ref<> smart pointer class is now thread-safe.

* Require C11 compliant compiler.

* Ref<> class: Implemented lock-free & wait-free thread-safety.

* Ref<> class: Use portable format specifier %p for pointers.

* Bumped version (2.1.1.svn56).

1 /*
2 * Copyright (c) 2014 - 2020 Christian Schoenebeck
3 *
4 * http://www.linuxsampler.org
5 *
6 * This file is part of LinuxSampler and released under the same terms.
7 * See README file for details.
8 */
9
10 #ifndef LS_REF_H
11 #define LS_REF_H
12
13 #include "global.h"
14
15 #include <set>
16 #include <stdio.h>
17 #if __cplusplus >= 201103L && !CONFIG_NO_CPP11STL
18 # include <type_traits> // for std::enable_if and std::is_same
19 #endif
20
21 // You may enable this while developing or at least when you encounter any kind
22 // of crashes or other misbehaviors in conjunction with Ref class guarded code.
23 // Enabling the following macro will add a bunch of sanity checks for easing
24 // debugging of such issues, however it comes with the cost that everything will
25 // be much slower.
26 #define LS_REF_ASSERT_MODE 0
27
28 // Enable this for VERY verbose debug messages for debugging deep issues with
29 // Ref class.
30 #define LS_REF_VERBOSE_DEBUG_MSG 0
31
32 #if LS_REF_ASSERT_MODE
33 # warning LS_REF_ASSERT_MODE is enabled which will decrease runtime efficiency!
34 # include <assert.h>
35 #endif
36
37 // Whether the Ref<> class shall be thread safe. Performance penalty should not
38 // be measurable in practice due to non blocking, lock-free atomic add, sub and
39 // CAS instructions being used, so this is recommended and enabled by default.
40 #ifndef LS_REF_ATOMIC
41 # define LS_REF_ATOMIC 1
42 #endif
43
44 //NOTE: We are using our own atomic implementation instead of std::atomic of the
45 // C++ STL, because our atomic implementation is lock-free and wait-free. The
46 // C++ standard just recommends, but does not guarantee lock-free implementation
47 // (state of the C++20 standard as of 2020-04-27):
48 // https://en.cppreference.com/w/cpp/atomic/atomic/is_lock_free
49 #if LS_REF_ATOMIC
50 # include "atomic.h"
51 # include <stdatomic.h>
52 # if __STDC_NO_ATOMICS__
53 # undef LS_REF_ATOMIC
54 # warning Ref<> class will not be thread safe, since compiler does not support C11 atomics!
55 # endif
56 #else
57 # warning Ref<> class will not be thread safe (feature explicitly disabled)!
58 #endif
59
60 #if LS_REF_ASSERT_MODE && LS_REF_ATOMIC
61 # include "Mutex.h"
62 #endif
63
64 namespace LinuxSampler {
65
66 template<typename T, typename T_BASE> class Ref;
67
68 #if LS_REF_ASSERT_MODE
69 extern std::set<void*> _allRefPtrs;
70 #endif
71 #if LS_REF_ASSERT_MODE && LS_REF_ATOMIC
72 extern Mutex _allRefPtrsMutex;
73 #endif
74
75 /**
76 * Exists just for implementation detail purpose, you cannot use it
77 * directly. Use its derived template class Ref instead.
78 *
79 * @see Ref
80 */
81 template<typename T_BASE>
82 class RefBase {
83 public:
84 template<typename T_BASE1>
85 class _RefCounter {
86 public:
87 _RefCounter(T_BASE1* p, int refs, bool released) :
88 #if LS_REF_ATOMIC
89 references(ATOMIC_INIT(refs)),
90 #else
91 references(refs),
92 #endif
93 ptr(p)
94 {
95 #if LS_REF_ATOMIC
96 atomic_store(&zombi, released);
97 #endif
98 #if LS_REF_VERBOSE_DEBUG_MSG
99 printf("Ref %p: new counter (refs=%d)\n", ptr,
100 #if LS_REF_ATOMIC
101 atomic_read(&references)
102 #else
103 references
104 #endif
105 );
106 #endif
107 #if LS_REF_ASSERT_MODE
108 assert(p);
109 assert(refs > 0);
110 assert(!released);
111 {
112 #if LS_REF_ATOMIC
113 LockGuard lock(_allRefPtrsMutex);
114 #endif
115 assert(!_allRefPtrs.count(p));
116 _allRefPtrs.insert(p);
117 }
118 #endif
119 }
120
121 virtual ~_RefCounter() {
122 #if LS_REF_VERBOSE_DEBUG_MSG
123 printf("Ref %p: counter destructor (refs=%d)\n", ptr,
124 #if LS_REF_ATOMIC
125 atomic_read(&references)
126 #else
127 references
128 #endif
129 );
130 #endif
131 fflush(stdout);
132 }
133
134 void retain() {
135 #if LS_REF_ATOMIC
136 atomic_inc(&references);
137 #else
138 references++;
139 #endif
140 #if LS_REF_VERBOSE_DEBUG_MSG
141 printf("Ref %p: retain (refs=%d)\n", ptr,
142 #if LS_REF_ATOMIC
143 atomic_read(&references)
144 #else
145 references
146 #endif
147 );
148 #endif
149 }
150
151 void release() {
152 #if LS_REF_ATOMIC
153 bool zero = atomic_dec_and_test(&references);
154 # if LS_REF_VERBOSE_DEBUG_MSG
155 printf("Ref %p: release (zero=%d)\n", ptr, zero);
156 # endif
157 if (!zero) return;
158 bool expect = false;
159 bool release = atomic_compare_exchange_strong(&zombi, &expect, true);
160 if (release) deletePtr();
161 #else
162 if (!references) return;
163 references--;
164 # if LS_REF_VERBOSE_DEBUG_MSG
165 printf("Ref %p: release (refs=%d)\n", ptr, references);
166 # endif
167 if (!references) deletePtr();
168 #endif // LS_REF_ATOMIC
169 }
170 //protected:
171 void deletePtr() {
172 #if LS_REF_VERBOSE_DEBUG_MSG
173 printf("RefCounter %p: deletePtr() (refs=%d)\n", ptr,
174 #if LS_REF_ATOMIC
175 atomic_read(&references)
176 #else
177 references
178 #endif
179 );
180 #endif
181 #if LS_REF_ASSERT_MODE
182 # if LS_REF_ATOMIC
183 assert(!atomic_read(&references));
184 # else
185 assert(!references);
186 # endif
187 {
188 #if LS_REF_ATOMIC
189 LockGuard lock(_allRefPtrsMutex);
190 #endif
191 _allRefPtrs.erase(ptr);
192 }
193 #endif
194 delete ptr;
195 delete this;
196 }
197
198 #if LS_REF_ATOMIC
199 atomic_t references;
200 atomic_bool zombi; ///< @c false if not released yet, @c true once @c ptr was released
201 #else
202 int references;
203 #endif
204 T_BASE1* ptr;
205 //friend class ... todo
206 };
207 typedef _RefCounter<T_BASE> RefCounter;
208
209 virtual ~RefBase() {
210 if (refCounter) refCounter->release();
211 refCounter = NULL;
212 }
213
214 //protected:
215 RefCounter* refCounter;
216 //friend class Ref<T_BASE, T_BASE>;
217
218 protected:
219 RefBase() : refCounter(NULL) {
220 #if LS_REF_VERBOSE_DEBUG_MSG
221 printf("(RefBase empty ctor)\n");
222 #endif
223 }
224 /*
225 RefBase(RefCounter* rc) {
226 refCounter = rc;
227 }
228
229 RefBase(const RefBase& r) {
230 refCounter = r.refCounter;
231 if (refCounter) refCounter->retain();
232 }
233 */
234 };
235
236 /**
237 * Replicates a std::shared_ptr template class. Originally this class was
238 * introduced to avoid a build requirement of having a C++11 compliant
239 * compiler (std::shared_ptr was not part of the C++03 standard). This class
240 * had been preserved though due to some advantages over the STL
241 * implementation, most notably: unlike std::shared_ptr from the STL, this
242 * Ref<> class provides thread safety by using a (guaranteed) lock-free and
243 * wait-free implementation, which is relevant for real-time applications.
244 *
245 * In contrast to the STL implementation though this implementation here
246 * also supports copying references of derived, different types (in a type
247 * safe manner). You can achieve that by providing a second template
248 * argument (which is optional), for declaring a common subtype. For example
249 * the following code would not compile:
250 * @code
251 * void example(UILabel* pLabel) {
252 * Ref<UILabel> lbl = pLabel;
253 * Ref<UIWidget> w = lbl; // compile error, incompatible Ref types
254 * w->resize(16,300);
255 * }
256 * @endcode
257 * Whereas the following would work:
258 * @code
259 * void example(UILabel* pLabel) {
260 * Ref<UILabel,UIWidget> lbl = pLabel;
261 * Ref<UIWidget> w = lbl; // works (assuming that UILabel is a subclass of UIWidget)
262 * w->resize(16,300);
263 * }
264 * @endcode
265 * Like the STL's std::shared_ptr, this class also emulates raw pointer
266 * access and operators. With one addition: if used in the derived common
267 * subtype manner as shown above, access to the actual data and boolean
268 * operator will also check whether the underlying pointer (of the common
269 * subclass) can actually be casted safely to the objects main type (first
270 * template argument of this class). For example:
271 * @code
272 * void example(UILabel* pLabel) { // assuming pLabel is not NULL ...
273 * Ref<UILabel,UIWidget> lbl = pLabel;
274 * Ref<UIDialog,UIWidget> dlg = lbl;
275 * bool b1 = lbl; // will be true (assuming pLabel was not NULL)
276 * bool b2 = dlg; // will be false (assuming that UIDialog is not derived from UILabel)
277 * lbl->setText("foo"); // works
278 * dlg->showModal(); // would crash with -> operator providing a NULL pointer
279 * }
280 * @endcode
281 * Like with std::shared_ptr you must be @b very cautious that you
282 * initialize only one Ref class object directly with the same raw pointer.
283 * If you forget this fundamental rule somewhere, your code will crash!
284 * @code
285 * UIWidget* ptr = new UIWidget();
286 * Ref<UIWidget> w1 = ptr;
287 * Ref<UIWidget> w2 = w1; // this is OK, copy from a Ref object
288 * Ref<UIWidget> w3 = ptr; // illegal! 2nd direct init from same raw pointer. This will crash!
289 * @endcode
290 * It would be possible to write an implementation of the Ref class that
291 * could handle the case above as well without crashing, however it would be
292 * too slow for practice. Because it would require a global lookup table
293 * maintaining all memory pointers which are currently already guarded by
294 * this class. Plus it would need an expensive synchronization to prevent
295 * concurrent access on that global lookup table.
296 */
297 template<typename T, typename T_BASE = T>
298 class Ref : public RefBase<T_BASE> {
299 public:
300 typedef RefBase<T_BASE> RefBaseT;
301 typedef typename RefBase<T_BASE>::RefCounter RefCounter;
302
303 Ref() : RefBaseT() {
304 #if LS_REF_VERBOSE_DEBUG_MSG
305 printf("Ref empty ctor Ref:%p\n", this);
306 #endif
307 }
308
309 #if __cplusplus >= 201103L && !CONFIG_NO_CPP11STL
310 template<std::enable_if<!std::is_same<T_BASE, T>::value>* = NULL> // prevent compiler error if T == T_Base (due to method signature duplicate)
311 #endif
312 Ref(const T_BASE* p) : RefBaseT() {
313 #if LS_REF_VERBOSE_DEBUG_MSG
314 printf("Ref base ptr ctor Ref:%p <- p:%p\n", this, p);
315 #endif
316 RefBaseT::refCounter = p ? new RefCounter((T_BASE*)p, 1, false) : NULL;
317 }
318
319 Ref(const T* p) : RefBaseT() {
320 #if LS_REF_VERBOSE_DEBUG_MSG
321 printf("Ref main ptr ctor Ref:%p <- p:%p\n", this, p);
322 #endif
323 RefBaseT::refCounter = p ? new RefCounter((T*)p, 1, false) : NULL;
324 }
325
326 Ref(const RefBaseT& r) : RefBaseT() {
327 #if LS_REF_VERBOSE_DEBUG_MSG
328 printf("Ref base ref ctor Ref:%p <- Ref:%p\n", this, &r);
329 #endif
330 RefBaseT::refCounter = r.refCounter;
331 if (RefBaseT::refCounter)
332 RefBaseT::refCounter->retain();
333 }
334
335 Ref(const Ref& r) : RefBaseT() {
336 #if LS_REF_VERBOSE_DEBUG_MSG
337 printf("Ref main ref ctor Ref:%p <- Ref:%p\n", this, &r);
338 #endif
339 RefBaseT::refCounter = r.refCounter;
340 if (RefBaseT::refCounter)
341 RefBaseT::refCounter->retain();
342 }
343
344 inline T* operator->() {
345 return dynamic_cast<T*>( RefBaseT::refCounter->ptr );
346 }
347
348 inline const T* operator->() const {
349 return dynamic_cast<const T*>( RefBaseT::refCounter->ptr );
350 }
351
352 inline T& operator*() {
353 return *dynamic_cast<T*>( RefBaseT::refCounter->ptr );
354 }
355
356 inline const T& operator*() const {
357 return *dynamic_cast<const T*>( RefBaseT::refCounter->ptr );
358 }
359
360 inline bool operator==(const RefBaseT& other) const {
361 return RefBaseT::refCounter == other.refCounter;
362 }
363
364 inline bool operator!=(const RefBaseT& other) const {
365 return RefBaseT::refCounter != other.refCounter;
366 }
367
368 inline operator bool() const {
369 return RefBaseT::refCounter && RefBaseT::refCounter->ptr &&
370 dynamic_cast<const T*>( RefBaseT::refCounter->ptr );
371 }
372
373 inline bool operator!() const {
374 return !( RefBaseT::refCounter && RefBaseT::refCounter->ptr &&
375 dynamic_cast<const T*>( RefBaseT::refCounter->ptr ) );
376 }
377
378 /*
379 inline operator RefBaseT&() {
380 return *this;
381 }
382
383 inline operator const RefBaseT&() const {
384 return *this;
385 }
386 */
387 inline bool isEquivalent(const RefBaseT& other) const {
388 if (static_cast<const RefBaseT*>(this) == &other)
389 return true;
390 return (RefBaseT::refCounter == other.refCounter);
391 }
392
393 inline bool isEquivalent(const T_BASE* const other) const {
394 if (!other) return !RefBaseT::refCounter;
395 if (!RefBaseT::refCounter) return false;
396 return other == RefBaseT::refCounter->ptr;
397 }
398
399 Ref<T,T_BASE>& operator=(const RefBaseT& other) {
400 #if LS_REF_VERBOSE_DEBUG_MSG
401 printf("Ref base ref assignment Ref:%p <- Ref:%p\n", this, &other);
402 #endif
403 if (isEquivalent(other)) {
404 #if LS_REF_VERBOSE_DEBUG_MSG
405 printf("Ref %p WRN: equivalent ref assignment ignored.\n", this);
406 #endif
407 return *this;
408 }
409 if (RefBaseT::refCounter) {
410 RefBaseT::refCounter->release();
411 RefBaseT::refCounter = NULL;
412 }
413 RefBaseT::refCounter = other.refCounter;
414 if (RefBaseT::refCounter)
415 RefBaseT::refCounter->retain();
416 return *this;
417 }
418
419 Ref<T,T_BASE>& operator=(const Ref<T,T_BASE>& other) {
420 #if LS_REF_VERBOSE_DEBUG_MSG
421 printf("Ref main ref assignment Ref:%p <- Ref:%p\n", this, &other);
422 #endif
423 if (isEquivalent(other)) {
424 #if LS_REF_VERBOSE_DEBUG_MSG
425 printf("Ref %p WRN: equivalent ref assignment ignored.\n", this);
426 #endif
427 return *this;
428 }
429 if (RefBaseT::refCounter) {
430 RefBaseT::refCounter->release();
431 RefBaseT::refCounter = NULL;
432 }
433 RefBaseT::refCounter = other.refCounter;
434 if (RefBaseT::refCounter)
435 RefBaseT::refCounter->retain();
436 return *this;
437 }
438
439 Ref<T,T_BASE>& operator=(const T* p) {
440 #if LS_REF_VERBOSE_DEBUG_MSG
441 printf("Ref main ptr assignment Ref:%p <- p:%p\n", this, p);
442 #endif
443 if (isEquivalent(p)) {
444 #if LS_REF_VERBOSE_DEBUG_MSG
445 printf("Ref %p WRN: equivalent ptr assignment ignored.\n", this);
446 #endif
447 return *this;
448 }
449 if (RefBaseT::refCounter) {
450 RefBaseT::refCounter->release();
451 RefBaseT::refCounter = NULL;
452 }
453 RefBaseT::refCounter = p ? new RefCounter((T*)p, 1, false) : NULL;
454 #if LS_REF_VERBOSE_DEBUG_MSG
455 printf("Ref main ptr assignment done\n");
456 #endif
457 return *this;
458 }
459
460 #if __cplusplus >= 201103L && !CONFIG_NO_CPP11STL
461 template<std::enable_if<!std::is_same<T_BASE, T>::value>* = NULL> // prevent compiler error if T == T_Base (due to method signature duplicate)
462 #endif
463 Ref<T,T_BASE>& operator=(const T_BASE* p) {
464 #if LS_REF_VERBOSE_DEBUG_MSG
465 printf("Ref base ptr assignment Ref:%p <- p:%p\n", this, p);
466 #endif
467 if (isEquivalent(p)) {
468 #if LS_REF_VERBOSE_DEBUG_MSG
469 printf("Ref %p WRN: equivalent ptr assignment ignored.\n", this);
470 #endif
471 return *this;
472 }
473 if (RefBaseT::refCounter) {
474 RefBaseT::refCounter->release();
475 RefBaseT::refCounter = NULL;
476 }
477 RefBaseT::refCounter = p ? new RefCounter((T*)p, 1, false) : NULL;
478 return *this;
479 }
480 };
481
482 } // namespace LinuxSampler
483
484 #endif // LS_REF_H

  ViewVC Help
Powered by ViewVC