-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshared_obj_failed_impl.h
More file actions
382 lines (369 loc) · 15 KB
/
shared_obj_failed_impl.h
File metadata and controls
382 lines (369 loc) · 15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
#pragma once
// Copyright David Lawrence Bien 1997 - 2021.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt).
// shared_obj_failed_impl.h
// dbien
// 15APR2021
// This implements a shared object base class to allow reference counted objects that only cost a single pointer
// in the implementation.
// REVIEW: I tried this but I encountered a catch-22 with "automatic application of conversion operator to
// a templated vs. a non-templated application point" which causes this impl (as far as I can tell) to be
// not implementable in a type-safe manner.
// Back to the drawing board but saving it for later just in case.
__BIENUTIL_BEGIN_NAMESPACE
template < class t_TyT, bool t_fDtorNoExcept, bool t_fDtorAllowThrow >
class _SharedObjectRef;
// This allows us to support derived classes flexibly.
template < bool t_fDtorNoExcept >
class _SharedObjectUltimateBase;
// We must specify the value of t_fDtorNoExcept since t_TyT is the derived class we are defining and it is an undefined type at this point.
template < class t_TyT, bool t_fDtorNoExcept, bool t_fDtorAllowThrow = true >
class SharedObjectBase;
// CHasSharedObjectBase: Enforce no reference, a base class and the base class.
// Can't enforce is_nothrow_destructible_v< t_TyT > since t_TyT isn't a known class at this point.
template < class t_TyT >
concept CHasSharedObjectBase = !is_reference_v< t_TyT > &&
is_base_of_v< _SharedObjectUltimateBase< is_nothrow_destructible_v< remove_cv_t< t_TyT > > >, remove_cv_t< t_TyT > >;
// We glean everything from the t_TyT class since it has the appropriate base class.
// The const version of this object is templatized by "const t_TyT".
// The volative version of this object is templatized by "volatile t_TyT".
// And you can also use both const and volatile if that's yer goal.
template < CHasSharedObjectBase t_TyT >
class SharedPtr
{
typedef SharedPtr _TyThis;
template < CHasSharedObjectBase t_TyTOther > friend class SharedPtr;
public:
typedef t_TyT _TyT;
static constexpr bool s_fIsConstObject = is_const_v< t_TyT >;
static constexpr bool s_fIsVolatileObject = is_volatile_v< t_TyT >;
typedef remove_cv_t< _TyT > _TyTNonConstVolatile;
static constexpr bool s_fDtorNoExcept = is_nothrow_destructible_v< _TyT >;
static constexpr bool s_fDtorAllowThrow = _TyTNonConstVolatile::s_fDtorAllowThrow;
typedef _SharedObjectRef< _TyTNonConstVolatile, s_fDtorNoExcept, s_fDtorAllowThrow > _TySharedObjectRef;
// typedef typename _TyTNonConstVolatile::_TySharedObjectRef _TySharedObjectRef;
typedef typename _TyTNonConstVolatile::_TySharedObjectBase _TySharedObjectBase;
~SharedPtr() noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
{
if ( m_pt ) // We are assume that re-entering this method cannot occur even if m_pt->Release() ends up throwing in ~t_TyT().
m_pt->Release();
}
SharedPtr() noexcept = default;
// Copy construct and assign:
SharedPtr( SharedPtr const & _r ) noexcept
: m_pt( _r.m_pt )
{
if ( m_pt )
m_pt->AddRef();
}
// This may throw because we must release any existing referece.
_TyThis & operator = ( SharedPtr const & _r ) noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
{
Clear();
if ( _r.m_pt )
{
m_pt = _r.m_pt;
m_pt->AddRef();
}
return *this;
}
// We allow initialization from a less or equally cv-qualified object SharedPtr that has the base
// class _TyT - as we understand that _TySharedObjectBase has a virtual destructor.
template < class t_TyTOther >
SharedPtr( SharedPtr< t_TyTOther > const & _rO ) noexcept
requires( ( s_fIsConstObject >= is_const_v< t_TyTOther > ) &&
( s_fIsVolatileObject >= is_volatile_v< t_TyTOther > ) &&
is_base_of_v< _TyTNonConstVolatile, remove_cv_t< t_TyTOther > > )
#if 0 // We should just be able to static_cast<> and things should work - also it's a good litmus for my requirements below - they should match.
: m_pt( static_cast< _TyTNonConstVolatile * >( const_cast< remove_cv_t< t_TyTOther > * >( _rO.m_pt ) ) )
#else //1
: m_pt( static_cast< _TyT * >( _rO.m_pt ) )
#endif //1
{
if ( m_pt )
m_pt->AddRef();
}
// Similar as above for assignment - might throw due to Clear().
template < class t_TyTOther>
_TyThis & operator =( SharedPtr< t_TyTOther > const & _rO ) noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
requires( ( s_fIsConstObject >= is_const_v< t_TyTOther > ) &&
( s_fIsVolatileObject >= is_volatile_v< t_TyTOther > ) &&
is_base_of_v< _TyTNonConstVolatile, remove_cv_t< t_TyTOther > > )
{
Clear();
if ( _rO.m_pt )
{
m_pt = static_cast< _TyT * >( _rO.m_pt );
m_pt->AddRef();
}
return *this;
}
// Movement construct and assign:
// No call to AddRef or Release() is necessary for the move constructor which is nice.
SharedPtr( SharedPtr && _rr ) noexcept
{
swap( _rr );
}
// This may throw because we must release any existing referece.
_TyThis & operator = ( SharedPtr && _rr ) noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
{
Clear();
swap( _rr );
return *this;
}
// As with copy construct and assign we allow move construct and assign from an object base on _TyT.
template < class t_TyTOther >
SharedPtr( SharedPtr< t_TyTOther > && _rrO ) noexcept
requires( ( s_fIsConstObject >= is_const_v< t_TyTOther > ) &&
( s_fIsVolatileObject >= is_volatile_v< t_TyTOther > ) &&
is_base_of_v< _TyTNonConstVolatile, remove_cv_t< t_TyTOther > > )
{
if ( _rrO.m_pt )
{
m_pt = static_cast< _TyT * >( _rrO.m_pt );
_rrO.m_pt = nullptr;
}
}
// Similar as above for assignment - might throw due to Clear().
template < class t_TyTOther>
_TyThis & operator =( SharedPtr< t_TyTOther > && _rrO ) noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
requires( ( s_fIsConstObject >= is_const_v< t_TyTOther > ) &&
( s_fIsVolatileObject >= is_volatile_v< t_TyTOther > ) &&
is_base_of_v< _TyTNonConstVolatile, remove_cv_t< t_TyTOther > > )
{
Clear();
if ( _rrO.m_pt )
{
m_pt = static_cast< _TyT * >( _rrO.m_pt );
_rrO.m_pt = nullptr;
}
return *this;
}
void swap( _TyThis & _r ) noexcept
{
std::swap( m_pt, _r.m_pt );
}
// Type conversions:
operator _TySharedObjectRef&()
{
return reinterpret_cast< _TySharedObjectRef& >( m_pt );
}
// Accessors:
_TyT * operator ->() const noexcept
{
return m_pt; // may be null...
}
_TyT & operator *() const noexcept
{
return *m_pt; // may be a null reference...
}
// operations:
void Clear() noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
{
if ( m_pt )
{
_TyT * pt = m_pt;
m_pt = 0; // throw safety.
pt->Release();
}
}
protected:
// Store the pointer const-correct:
_TyT * m_pt{nullptr};
};
template < bool t_fDtorNoExcept >
class _SharedObjectUltimateBase
{
typedef _SharedObjectUltimateBase _TyThis;
protected:
virtual ~_SharedObjectUltimateBase() noexcept( t_fDtorNoExcept ) = default;
};
// SharedObjectBase:
// We templatize by whether the destructor of the most derived object can throw.
// The second template argument is whether we allow a throwing destructor to throw out of _DeleteSelf() or we catch locally.
template < class t_TyT, bool t_fDtorNoExcept, bool t_fDtorAllowThrow >
class SharedObjectBase : public _SharedObjectUltimateBase< t_fDtorNoExcept >
{
typedef SharedObjectBase _TyThis;
typedef _SharedObjectUltimateBase< t_fDtorNoExcept > _TyBase;
public:
typedef t_TyT _TyT;
static constexpr bool s_fDtorNoExcept = t_fDtorNoExcept;
static constexpr bool s_fDtorAllowThrow = t_fDtorAllowThrow;
typedef _SharedObjectRef< _TyT, s_fDtorNoExcept, s_fDtorAllowThrow > _TySharedObjectRef;
typedef _TyThis _TySharedObjectBase;
// We'll use the supposed "fastest" integer here - since we know our pointer is 64bit.
typedef int_fast32_t _TyRefValueType;
#if IS_MULTITHREADED_BUILD
// Assume that if we are in the multithreaded build that we want a threadsafe increment/decrement.
typedef atomic< _TyRefValueType > _TyRefMemberType;
#else //!IS_MULTITHREADED_BUILD
typedef _TyRefValueType _TyRefMemberType;
#endif //!IS_MULTITHREADED_BUILD
SharedObjectBase() noexcept
{
}
// Trivial copy constructor allows object to be copy constructible.
SharedObjectBase( const SharedObjectBase & ) noexcept
{
}
_TyRefValueType NGetRefCount() const volatile noexcept
{
return m_nRefCount;
}
// We have declared m_nRefCount mutable so that we can declare these const and volatile and then allow the code to be generally cv-correct.
// This allows us to add-ref an object even though its element is const or volatile - which is what we want.
_TyRefValueType AddRef() const volatile noexcept
{
#if IS_MULTITHREADED_BUILD
return ++m_nRefCount;
#else //!IS_MULTITHREADED_BUILD
return ++const_cast< _TyRefValueType & >( m_nRefCount );
#endif //!IS_MULTITHREADED_BUILD
}
_TyRefValueType Release() const volatile noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
{
#if IS_MULTITHREADED_BUILD
_TyRefValueType nRef = --m_nRefCount;
#else //!IS_MULTITHREADED_BUILD
_TyRefValueType nRef = --const_cast< _TyRefValueType & >( m_nRefCount );
#endif //!IS_MULTITHREADED_BUILD
if ( !nRef )
_DeleteSelf();
return nRef;
}
// To do this correctly we must override new and delete. Note that we do not support array new[].
static void * operator new( size_t _nbySize, _TySharedObjectRef & _sor ) noexcept(false)
{
void * pvMem = ::operator new( _nbySize
#if TRACK_SHARED_OBJECT_ALLOCATIONS
// We are tracking shared object memory allocation but the caller didn't use SO_NEW().
,__FILE__, __LINE__
#endif //TRACK_SHARED_OBJECT_ALLOCATIONS
);
// Note that we are setting in a uninitialized object - just memory. However SharedObjectRef has no effective lifetime and is a trivial object so that doesn't matter.
_sor.ResetSharedObjectBase( (SharedObjectBase*)pvMem );
return pvMem;
}
template < class t_TyOther >
static void * operator new( size_t _nbySize, _SharedObjectRef< t_TyOther, s_fDtorAllowThrow, s_fDtorNoExcept > & _sor ) noexcept(false)
{
void * pvMem = ::operator new( _nbySize
#if TRACK_SHARED_OBJECT_ALLOCATIONS
// We are tracking shared object memory allocation but the caller didn't use SO_NEW().
,__FILE__, __LINE__
#endif //TRACK_SHARED_OBJECT_ALLOCATIONS
);
// Note that we are setting in a uninitialized object - just memory. However SharedObjectRef has no effective lifetime and is a trivial object so that doesn't matter.
_sor.ResetSharedObjectBase( (SharedObjectBase*)pvMem );
return pvMem;
}
#if TRACK_SHARED_OBJECT_ALLOCATIONS
// This allows us to associate the creating file/line with the allocation - by passing it to the eventual call to new().
void* operator new( size_t _nbySize, _TySharedObjectRef & _sor, const char * _szFilename, int _nSourceLine ) noexcept(false)
{
void * pvMem = ::operator new( _nbySize, _szFilename, _nSourceLine );
_sor.ResetSharedObjectBase( (SharedObjectBase*)pvMem );
return pvMem;
}
#endif //TRACK_SHARED_OBJECT_ALLOCATIONS
// We can only enter this delete operator if we fail inside of the corresonding new operator.
//static void operator delete( void * _pv, _TySharedObjectRef & _sor ) noexcept
//{
// _sor._ClearSharedObjectBase( (SharedObjectBase*)_pv ); // Clear any partially constructed object but only if it equals _pv;
// ::operator delete( _pv );
//}
template < class t_TyOther >
static void operator delete( void * _pv, _SharedObjectRef< t_TyOther, s_fDtorAllowThrow, s_fDtorNoExcept > & _sor ) noexcept
{
_sor._ClearSharedObjectBase( (SharedObjectBase*)_pv ); // Clear any partially constructed object but only if it equals _pv;
::operator delete( _pv );
}
#if TRACK_SHARED_OBJECT_ALLOCATIONS
// If we throw inside of the corresponding new operator then this will be called:
void operator delete( void * _pv, _SharedObjectRef & _sor, const char * _szFilename, int _nSourceLine ) noexcept
{
_sor._ClearSharedObjectBase( (SharedObjectBase*)_pv ); // Clear any partially constructed object but only if it equals _pv;
::delete( _pv );
}
#endif //TRACK_SHARED_OBJECT_ALLOCATIONS
// The normal way an obect's memory is deleted...
static void operator delete( void* _pv ) noexcept
{
::operator delete( _pv );
}
protected:
mutable _TyRefMemberType m_nRefCount{1}; // We make this mutable allowing AddRef() and Release() to be "const" methods.
~SharedObjectBase() noexcept( s_fDtorNoExcept ) override = default;
// Need to declare const volatile since might get called by Release().
// If we are the last reference to an object then we are the only thread accessing that object.
void _DeleteSelf() const volatile noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
requires( s_fDtorNoExcept )
{
delete this; // destructor can't throw so nothing special to do here.
}
void _DeleteSelf() const volatile noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
requires( !s_fDtorNoExcept )
{
const bool kfInUnwinding = !s_fDtorAllowThrow || !!std::uncaught_exceptions();
try
{
delete this;
}
catch ( std::exception const & )
{
if ( !kfInUnwinding )
throw; // let someone else deal with this.
// else just eat the exception silently - mmmm yummy!
}
}
};
template < class t_TyT, bool t_fDtorNoExcept, bool t_fDtorAllowThrow >
class _SharedObjectRef
{
typedef _SharedObjectRef _TyThis;
public:
typedef t_TyT _TyT;
static constexpr bool s_fDtorNoExcept = t_fDtorNoExcept;
static constexpr bool s_fDtorAllowThrow = t_fDtorAllowThrow;
typedef SharedObjectBase< _TyT, s_fDtorNoExcept, s_fDtorAllowThrow > _TySharedObjectBase;
friend _TySharedObjectBase;
_SharedObjectRef() = delete; // We only ever cast to this object - we never create it.
_SharedObjectRef( _SharedObjectRef const & ) noexcept = default; // we are copyable.
protected:
_TySharedObjectBase * PGetSharedObjectBase() const noexcept
{
return m_psob;
}
// This merely clears the *value* of m_psob. This is called when an exception is encountered during construction.
// The indication is that we have an object here that is unowned - it is owned by C++ construction not have completed.
void _ClearSharedObjectBase( _TySharedObjectBase * _psob ) noexcept
{
if ( _psob == m_psob )
m_psob = nullptr;
}
void ResetSharedObjectBase( _TySharedObjectBase * _psob ) noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
requires( s_fDtorNoExcept || !s_fDtorAllowThrow )
{
// no throwing out on destruct so a bit simpler code:
if ( m_psob )
(void)m_psob->Release();
m_psob = _psob;
}
void ResetSharedObjectBase( _TySharedObjectBase * _psob ) noexcept( s_fDtorNoExcept || !s_fDtorAllowThrow )
requires( !s_fDtorNoExcept && s_fDtorAllowThrow )
{
// destructor may throw:
if ( m_psob )
{
_TySharedObjectBase * psobRelease = m_psob;
m_psob = nullptr; // throw-safety
(void)psobRelease->Release();
}
m_psob = _psob;
}
_TySharedObjectBase * m_psob/* {nullptr} */; // No reason to initialize because we are never constructed.
};
__BIENUTIL_END_NAMESPACE