-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshared_obj.h
More file actions
302 lines (289 loc) · 12.5 KB
/
shared_obj.h
File metadata and controls
302 lines (289 loc) · 12.5 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
#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.h
// dbien
// 15APR2021
// This implements a shared object base class to allow reference counted objects that only cost a single pointer
// in the implementation.
__BIENUTIL_BEGIN_NAMESPACE
// We must specify the value of t_kfDtorNoExcept since t_TyT is the derived class we are defining and it is an undefined type at this point.
template < bool t_kfDtorNoExcept, bool t_kfDtorAllowThrow = 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< SharedObjectBase< is_nothrow_destructible_v< remove_cv_t< t_TyT > >, false >, remove_cv_t< t_TyT > > ||
is_base_of_v< SharedObjectBase< is_nothrow_destructible_v< remove_cv_t< t_TyT > >, true >, 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_kfIsConstObject = is_const_v< t_TyT >;
static constexpr bool s_kfIsVolatileObject = is_volatile_v< t_TyT >;
typedef remove_cv_t< _TyT > _TyTNonConstVolatile;
static constexpr bool s_kfDtorNoExcept = is_nothrow_destructible_v< _TyTNonConstVolatile >;
static constexpr bool s_kfDtorAllowThrow = _TyTNonConstVolatile::s_kfDtorAllowThrow;
static constexpr bool s_kfIsNoThrowConstructible = is_nothrow_constructible_v< _TyTNonConstVolatile >;
// Get the ultimate shared object base:
typedef typename _TyTNonConstVolatile::_TySharedObjectBase _TySharedObjectBase;
~SharedPtr() noexcept( s_kfDtorNoExcept || !s_kfDtorAllowThrow )
{
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_kfDtorNoExcept || !s_kfDtorAllowThrow )
{
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_kfIsConstObject >= is_const_v< t_TyTOther > ) &&
( s_kfIsVolatileObject >= 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_kfDtorNoExcept || !s_kfDtorAllowThrow )
requires( ( s_kfIsConstObject >= is_const_v< t_TyTOther > ) &&
( s_kfIsVolatileObject >= 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_kfDtorNoExcept || !s_kfDtorAllowThrow )
{
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_kfIsConstObject >= is_const_v< t_TyTOther > ) &&
( s_kfIsVolatileObject >= 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_kfDtorNoExcept || !s_kfDtorAllowThrow )
requires( ( s_kfIsConstObject >= is_const_v< t_TyTOther > ) &&
( s_kfIsVolatileObject >= 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 );
}
// In-place construction constructors:
// In-place construction of the same type as the declared _TyT.
template < class ... t_TysArgs >
explicit SharedPtr( std::in_place_t, t_TysArgs && ... _args ) noexcept( s_kfIsNoThrowConstructible )
{
m_pt = DBG_NEW _TyT( std::forward< t_TysArgs >( _args ) ... );
}
// In-place construction of a derived type. In this case we expect a non-qualified type in t_TyDerived, though if I had reason to change that...
template < class t_TyDerived, class ... t_TysArgs >
explicit SharedPtr( std::in_place_type_t< t_TyDerived >, t_TysArgs && ... _args ) noexcept( is_nothrow_constructible_v< t_TyDerived > )
{
static_assert( is_same_v< t_TyDerived, remove_cv_t< t_TyDerived > > ); // Have this here so that we participate in overload resolution and then fail.
m_pt = DBG_NEW t_TyDerived( std::forward< t_TysArgs >( _args ) ... );
}
// Emplacement: We allow creation of this object and derived objects.
// We return the non-cv-qualified object reference - allowing full access to the object for the caller.
// This is a design decision and should be considered in the future.
// We will clear any existing object first. The side effect of this is that the SharedPtr will be empty if we throw
// in the existing reference's destructor or in the constructor of the created object.
template < class ... t_TysArgs >
_TyTNonConstVolatile & emplace( t_TysArgs &&... _args ) noexcept( s_kfIsNoThrowConstructible && ( s_kfDtorNoExcept || !s_kfDtorAllowThrow ) )
{
Clear();
_TyTNonConstVolatile * pt = DBG_NEW _TyTNonConstVolatile( std::forward< t_TysArgs >( _args ) ... );
Assert( 1 == pt->NGetRefCount() );
m_pt = pt;
return *pt;
}
// Create a derived class and assign to m_pt.
// In this case we enforce that the cv-qualified type given for t_TyDerived is compatible with the
// cv-qualification of _TyT. This ensures that callers "get what they expect". Now it could be useful to
// do it the other way as well - but this is my first idea on this.
// We still return a reference to a non-cv-qualified object to allow the caller to further modify as necessary as the
// caller must be the only thread with access to this object at the time of creation.
template < class t_TyDerived, class ... t_TysArgs >
remove_cv_t< t_TyDerived > & emplaceDerived( t_TysArgs && ... _args ) noexcept( is_nothrow_constructible_v< t_TyDerived > && ( s_kfDtorNoExcept || !s_kfDtorAllowThrow ) )
{
typedef remove_cv_t< t_TyDerived > _TyDerivedNoCV;
Clear();
_TyDerivedNoCV * ptDerivedNoCV = DBG_NEW _TyDerivedNoCV( std::forward< t_TysArgs >( _args ) ... );
Assert( 1 == ptDerivedNoCV->NGetRefCount() );
m_pt = static_cast< _TyT * >( static_cast< t_TyDerived * >( ptDerivedNoCV ) );
return *ptDerivedNoCV;
}
// 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_kfDtorNoExcept || !s_kfDtorAllowThrow )
{
if ( m_pt )
{
_TyT * pt = m_pt;
m_pt = 0; // throw safety.
pt->Release();
}
}
protected:
// Store the pointer const-correct:
_TyT * m_pt{nullptr};
};
// 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.
// REVIEW: We could add a deleter object (which we would have to cascade up to the SharedPtr<> container) instead of just using
// global operator delete.
template < bool t_kfDtorNoExcept, bool t_kfDtorAllowThrow >
class SharedObjectBase
{
typedef SharedObjectBase _TyThis;
public:
static constexpr bool s_kfDtorNoExcept = t_kfDtorNoExcept;
static constexpr bool s_kfDtorAllowThrow = t_kfDtorAllowThrow;
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.
// We don't copy the ref count - leave it at one.
SharedObjectBase( const SharedObjectBase & ) noexcept
{
}
// We can't support swapping the reference count since there would be smart objects bound to those references on this object.
// We can't support move operations for the same reason - though the derived class support those.
_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_kfDtorNoExcept || !s_kfDtorAllowThrow )
{
#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;
}
protected:
virtual ~SharedObjectBase() noexcept( s_kfDtorNoExcept ) = 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_kfDtorNoExcept || !s_kfDtorAllowThrow )
requires( s_kfDtorNoExcept )
{
delete this; // destructor can't throw so nothing special to do here.
}
void _DeleteSelf() const volatile noexcept( s_kfDtorNoExcept || !s_kfDtorAllowThrow )
requires( !s_kfDtorNoExcept )
{
const bool kfInUnwinding = !s_kfDtorAllowThrow || !!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!
}
}
mutable _TyRefMemberType m_nRefCount{1}; // We make this mutable allowing AddRef() and Release() to be "const" methods.
};
__BIENUTIL_END_NAMESPACE