/[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 3769 - (show annotations) (download) (as text)
Fri May 15 20:16:39 2020 UTC (3 years, 11 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 17419 byte(s)
Ref<> class: Use C++ atomics instead of C atomics (as some compilers seem
to be unable to mix C atomics with C++ code, and the generated result
should be binary identical anyway).

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

  ViewVC Help
Powered by ViewVC