libsq3 2007.10.18
refcount.hpp
1#ifndef s11n_net_refcount_REFCOUNT_HPP_INCLUDED
2#define s11n_net_refcount_REFCOUNT_HPP_INCLUDED 1
3// reminders to self:
4// - think about lazyassptr<T> which lazily instantiates its
5// pointee. That would take a Constructor functor, providing symmetry
6// with rcptr<>.
7
8#include <map>
9
10
11/**
12 The refcount namespace encapsulates code for a reference-counted
13 smart pointer. It is capable of tracking and destroying objects and
14 arbitrary pointers (including void pointers) and destroying them
15 using a user-defined finalizer functor. This allows, e.g., the
16 reference-counted sharing of memory allocated via malloc() or by
17 third-party functions such as dlopen() or sqlite3_open().
18
19 This code is not generic, industrial-strength reference counting
20 and is as much an experiment as anything else.
21
22 Author: stephan at s11n dot net
23
24 License: Public Domain
25*/
26namespace refcount {
27
28
29 /**
30 A no-op "destructor" for use with rcptr.
31 */
33 {
34 /** Assigs t to 0 without deleting t. */
35 template <typename T>
36 void operator()( T * & t )
37 {
38 t = 0;
39 }
40 };
41
42 /**
43 The default destructor/cleanup functor for use with
44 rcptr<>.
45 */
47 {
48 /**
49 Calls delete t and assigns t to 0.
50
51 Specialized dtors need not call delete, but should
52 assign t to 0, as this simplifies some client code.
53
54 T must be non-CVP-qualified and for this
55 implementation (delete t) must be legal.
56 */
57 template <typename T>
58 void operator()( T * & t )
59 {
60 delete t;
61 t = 0;
62 }
63 };
64
65 /**
66 All classes in this namespace are "internal details" of the
67 classes in the refcount namespace, and should not be
68 directly used by client code.
69 */
70 namespace Detail
71 {
72 /**
73 Internal detail for dereferencing pointers.
74 */
75 template <typename T>
76 struct ref_type
77 {
78 /** Same as (T&). */
79 typedef T & type;
80 /** Returns *t. */
81 static type deref( T *t ) { return *t; }
82 };
83 /**
84 Internal detail for dereferencing pointers.
85 */
86 template <>
87 struct ref_type<void>
88 {
89 /** Same as (void*&). */
90 typedef void * & type;
91 /** Returns xx. */
92 static type deref( type xx ) { return xx; }
93 };
94 } // namespace Detail
95
96 /**
97 A bare-bones non-intrusive reference-counted pointer type
98 with the ability for the client to specify a
99 finalization/destruction functor for the pointed-to type.
100
101 HandleT must be a non-CVP-qualified type. As a special
102 case, if HandleT is void then some code in this class will
103 work a bit differently, notably the operator*(), because we
104 cannot form a reference to void. Void is supported because
105 (void *) is commonly used for opaque handles (e.g. libdl)
106 or multibyte string pointers (e.g. libsqlite3).
107
108 FinalizerT must be a type compatible with the
109 plain_delete_finalizer interface. A default-constructed
110 instance of that FinalizerT type will be created to
111 "finalize" an object when the reference count for that
112 object drops to zero. The exact behaviour of the FinalizerT
113 is not specified here, but semantically it must "finalize"
114 the object passed to it. The default finalizer simply
115 deletes the object, whereas a more advanced finalizer might
116 push the object into a garbage collection pool. For
117 purposes of this class, after finalization of an object,
118 client code (and this type) should no longer use the object
119 - it is considered to be destroyed.
120
121 This type does not currently have any built-in support for
122 copy-on-write, so all copies are extremely shallow.
123
124 Notes of Utmost Significance to Potential Users:
125
126 - Implicit conversions to/from HandleT are not implemented
127 after much deliberation on the subject. Clients will *have*
128 to know they're using this class, as opposed to a plain
129 pointer type. This is safest for everyone, IMO.
130
131 - Don't mix plain and rcptr-hosted pointers, as the rcptr
132 wrappers own the pointers and will clean them up, leaving
133 any unadorned pointers dangling.
134
135 - Thread safety: no special guarantees, along with lots of
136 caveats and potential gotchas.
137
138 - Don't mix different smart pointer types, not even
139 rcptrs with the same HandleT type but different
140 FinalizerT types. This will almost certainly bring about
141 the incorrect finalization of a pointer.
142
143 - The usage of a finalizer functor means that this type can
144 be used with arbitrary types, regardless of whether the
145 delete operation is legal or not on them. For example, the
146 client code for which this class was written uses a functor
147 to finalize sqlite3 database handles using the
148 sqlite3_close() function.
149
150
151
152 Design notes:
153
154 - While originally based off of the presentation of
155 rc-pointers in Meyers' "More Effective C++", Item 29, i
156 believe his approach to storing the reference count in his
157 RCIPtr class is flawed, as it allows multiple rc-pointers
158 to delete the same pointer. Consider:
159
160\code
161 typedef RCIPtr<MyType> myPtrType;
162 MyType * t = new MyType;
163 myPtrType x(t);
164 myPtrType y(t);
165\endcode
166
167 In theory, his presentation (admittedly 10+ years old now)
168 would cause a double-delete for that case. In this model,
169 that case is handled as if we had constructed y using y(x)
170 instead of y(t), so both x and y share the reference count.
171
172 - The reference count is stored in a static-space std::map,
173 and that map is specific to this type and its combination
174 of HandleT/FinalizerT types. If we made the map only
175 specific to the HandleT, then we would get
176 strange/undesired behaviour when we did:
177
178\code
179 rcptr<T1,finalizerT1> p1( new T1 );
180 rcptr<T2,finalizerT2> p2( p1.get() );
181\endcode
182
183 because the actual finalizer used would be the one for
184 which the rcptr is destroyed *last*. Since destruction
185 order is not always determinate, this mixture would be a
186 bad idea. Note that it is still illegal to add the same
187 pointer to multiple different shared pointer types. The
188 above example, while illegal, will at least cause
189 determinate behaviour: a double finalization (but the order
190 is still unspecified in the general case)!
191
192
193 Fundamental differences between rcptr and
194 boost::shared_ptr:
195
196 - rcptr::take() allows client to take ownership of a
197 pointer away from rcptr. According to the shared_ptr FAQ,
198 this isn't technically feasible in that class due to their
199 handling of the user-defined finalizer.
200
201 - rcptr has no explicit support for multi-threading.
202
203 - shared_ptr does not handle the following code "correctly"
204 (IMO):
205
206 typedef boost::shared_ptr<AStruct> SP;
207 SP sp1( new AStruct );
208 SP sp2( sp1.get() );
209 // sp1.use_count() is 1, not 2
210 // This causes a double deletion
211
212 rcptr handles that case transparently.
213
214 */
215 template <typename HandleT,
216 typename FinalizerT = plain_delete_finalizer>
217 class rcptr
218 {
219 public:
220 /**
221 The basic type of object pointed to.
222 */
223 typedef HandleT type;
224 /**
225 The basic pointer type.
226 */
228 /** The type of functor used to clean up pointer_type objects. */
229 typedef FinalizerT finalizer_type;
230 private:
231 mutable pointer_type m_ptr;
232 typedef int counter_type;
233 typedef std::map<pointer_type,counter_type> map_type;
234 /** Returns a shared map holding the reference
235 counts for all instances of pointer_type
236 tracked by this class. This is not
237 post-main() safe.
238 */
239 static map_type & map()
240 {
241 static map_type bob;
242 return bob;
243 }
244
245 /**
246 Decrements the reference count to ptr. If the
247 count goes to 0, an instance of finalizer_type is
248 used to "destruct" ptr. On success, the current
249 reference count is returned. If 0 is returned,
250 ptr should be considered invalid (though this
251 actually depends on finalizer_type's
252 implementation, the semantics are that destruction
253 leaves us with an unusable object). On error
254 (passed a null ptr), a number less than 0 is
255 returned.
256 */
257 static counter_type decrement( pointer_type & ptr )
258 {
259 if( ! ptr ) return false;
260 typename map_type::iterator it = map().find(ptr);
261 if( map().end() == it ) return false;
262 if( 0 == (*it).second ) return 0; // can happen, e.g., if take() is called.
263 counter_type rc = --(*it).second;
264 if ( 0 == rc )
265 {
266 map().erase( it );
267 finalizer_type()( ptr );
268 }
269 return rc;
270 }
271
272 /**
273 If ! ptr, does nothing, else it increases the
274 reference count for ptr by one. Returns the current
275 reference count (guaranteed to be 1 or higher) on
276 success, or a negative number if passed a null ptr.
277 */
278 static counter_type increment( pointer_type & ptr )
279 {
280 if( ! ptr ) return -1;
281 return ++(map()[ptr]);
282 }
283
284// bool safety_first()
285// {
286// if( ! this->m_ptr ) return false;
287// if( 0 == this->ref_count() )
288// { // dangling pointer, it seems
289// this->m_ptr = 0;
290// }
291// return 0 != this->m_ptr;
292// }
293 public:
294 /**
295 Transfers ownership of h, or allows h to
296 participate in ownership with other rcptr
297 objects pointing at h.
298 */
299 explicit rcptr( pointer_type h ) : m_ptr(h)
300 {
301 this->increment( this->m_ptr );
302 }
303 /**
304 rhs and this object will both manage the same
305 underlying pointer.
306 */
307 rcptr( rcptr const & rhs ) : m_ptr(rhs.m_ptr)
308 {
309 this->increment( this->m_ptr );
310 }
311 /**
312 First disowns any connected pointer (using
313 take(0)), then rhs and this object will
314 both manage the same underlying pointer. If
315 by chance rhs.get() == this->get() when
316 this function starts then this function
317 does nothing and has no side effects.
318 */
319 rcptr & operator=( rcptr const & rhs )
320 {
321 if( rhs.m_ptr == this->m_ptr ) return *this;
322 this->decrement( this->m_ptr );
323 this->m_ptr = rhs.m_ptr;
324 this->increment( this->m_ptr );
325 return *this;
326 }
327 /**
328 An empty shared pointer, useful only as a target of
329 assigment or take().
330 */
331 rcptr() : m_ptr(0)
332 {}
333
334 /**
335 Efficiently swaps this object and rhs, such that they
336 swap ownership of their underlying pointers.
337 This does not require fiddling with the reference
338 counts, so it is much faster than using an rcptr
339 copy to perform a swap.
340 */
341 void swap( rcptr & rhs )
342 {
343 if( this->m_ptr != rhs )
344 {
345 pointer_type x = this->m_ptr;
346 this->m_ptr = rhs.m_ptr;
347 rhs.m_ptr = x;
348 }
349 }
350
351
352 /**
353 See decrement();
354 */
356 {
357 this->decrement( this->m_ptr );
358 }
359
360 /** Returns (this->m_ptr == rhs.m_ptr). */
361 bool operator==( rcptr const & rhs ) const
362 {
363 return this->m_ptr == rhs.m_ptr;
364 }
365 /** Returns (this->m_ptr != rhs.m_ptr). */
366 bool operator!=( rcptr const & rhs ) const
367 {
368 return this->m_ptr != rhs.m_ptr;
369 }
370
371 /**
372 Returns this->get() < rhs.get(). Implemented so that
373 this type can be used as keys in STL containers.
374 */
375 bool operator<( rcptr const & rhs ) const
376 {
377 return this->m_ptr < rhs.m_ptr;
378 }
379
380 /** Returns this object's underlying pointer, which
381 may be 0. This does not transfer ownership. This
382 object still owns (or participates in the
383 ownership of) the returned pointer.
384 */
385 pointer_type get() const { return this->m_ptr; }
386
387 /**
388 Gives ownership of p to this object (or a
389 collection of like-types rcptr objects). Drops
390 ownership of this->get() before making the
391 takeover. If (this->get() == p) then this function
392 does nothing.
393 */
395 {
396 if( p == this->m_ptr ) return;
397 this->decrement( this->m_ptr );
398 this->increment( this->m_ptr = p );
399 }
400
401 /**
402 Transfers ownership of this->get() to the caller.
403
404 ALL rcptr<> objects which point to that object
405 (except this rcptr) STILL point to that object,
406 but they will not activate the destructor functor
407 when they die, so they are safe as long as they
408 remain unsued or are destroyed before the "raw"
409 pointer returned from this function is destroyed.
410 */
412 {
413 if( this->m_ptr )
414 {
415 pointer_type t = this->m_ptr;
416 this->map().erase( this->m_ptr );
417 this->m_ptr = 0;
418 return t;
419 }
420 return this->m_ptr;
421 }
422
423 /**
424 The same as this->get().
425 */
426 pointer_type operator->() const { return this->m_ptr; }
427
428
429 /**
430 reference_type is the same as (T&) unless T is void,
431 in which case it is the same as (void*&) because
432 (void&) is not legal.
433 */
435
436 /**
437 The same as *(this->get()). Behaviour is undefined
438 if (!this->get()). We would throw an exception, but
439 this code is specifically intended for use on
440 platforms where exceptions are not allowed or not
441 supported (e.g. some embedded platforms).
442
443 SPECIAL CASE: rcptr::type is void
444
445 If rcptr::type is void then this function returns a
446 refernce to a pointer instead of a reference. This
447 is to allow this type to work with (void*) handle
448 types, such as handles returned from dlopen() or
449 memory returned from malloc(). Finalizers for such
450 handles could call dlclose() or free(), as
451 appropriate.
452 */
454 {
455 return Detail::ref_type<type>::deref( this->m_ptr );
456 }
457 /**
458 Returns the number of references to this object's pointer,
459 or zero if no pointer is bound. This function should be
460 considered a debugging/informational function, and not
461 a "feature" of this type.
462
463 Complexity = that of a std::map lookup.
464 */
465 size_t ref_count() const
466 {
467 if( ! this->m_ptr ) return 0;
468 typename map_type::iterator it = map().find(this->m_ptr);
469 return ( map().end() == it ) ? 0 : (*it).second;
470 }
471
472 /**
473 Returns the same as (!this->get()).
474 */
475 bool empty() const { return 0 == this->m_ptr; }
476
477// Adding deep copy support requires a copy ctor/functor for our
478// pointee type, but this type should/must be usable with opaque
479// pointer handles as well as object pointers (e.g. sqlite3 db handles
480// and ncurses WINDOW handles). But if you did want to implement
481// copy(), here's how you might go about doing it...
482// /**
483// Makes a copy of p using (new type(*p)) and
484// transfers ownership of that copy to this
485// object. Further copies of this object will point to
486// that copy unless/until copy() is called on
487// them. This function is intended to simplify
488// implementation of copy-on-write. If p is null then
489// this object points to null.
490
491// To force an rcptr to copy its current pointer, simply
492// call ptr.copy( ptr.get() ).
493// */
494// void copy( pointer_type p )
495// {
496// pointer_type x = this->m_ptr;
497// if( p )
498// {
499// this->m_ptr = new type(*p);
500// this->increment( this->m_ptr );
501// }
502// else
503// {
504// this->m_ptr = 0;
505// }
506// this->decrement(x);
507// }
508// Some things to consider:
509// bool m_shareable;
510// bool shareable() const { return this->m_shareable; }
511// void shareable( bool s ) { this->m_shareable = s; }
512// bool shared() const { return this->ref_count() > 1; }
513
514
515 };
516
517
518} // namespaces
519
520
521#endif // s11n_net_refcount_REFCOUNT_HPP_INCLUDED
rcptr(rcptr const &rhs)
rhs and this object will both manage the same underlying pointer.
Definition: refcount.hpp:307
reference_type operator*() const
The same as *(this->get()).
Definition: refcount.hpp:453
pointer_type operator->() const
The same as this->get().
Definition: refcount.hpp:426
pointer_type take()
Transfers ownership of this->get() to the caller.
Definition: refcount.hpp:411
pointer_type get() const
Returns this object's underlying pointer, which may be 0.
Definition: refcount.hpp:385
void swap(rcptr &rhs)
Efficiently swaps this object and rhs, such that they swap ownership of their underlying pointers.
Definition: refcount.hpp:341
bool operator==(rcptr const &rhs) const
Returns (this->m_ptr == rhs.m_ptr).
Definition: refcount.hpp:361
rcptr(pointer_type h)
Transfers ownership of h, or allows h to participate in ownership with other rcptr objects pointing a...
Definition: refcount.hpp:299
rcptr & operator=(rcptr const &rhs)
First disowns any connected pointer (using take(0)), then rhs and this object will both manage the sa...
Definition: refcount.hpp:319
rcptr()
An empty shared pointer, useful only as a target of assigment or take().
Definition: refcount.hpp:331
FinalizerT finalizer_type
The type of functor used to clean up pointer_type objects.
Definition: refcount.hpp:229
void take(pointer_type p)
Gives ownership of p to this object (or a collection of like-types rcptr objects).
Definition: refcount.hpp:394
HandleT type
The basic type of object pointed to.
Definition: refcount.hpp:223
type * pointer_type
The basic pointer type.
Definition: refcount.hpp:227
~rcptr()
See decrement();.
Definition: refcount.hpp:355
Detail::ref_type< type >::type reference_type
reference_type is the same as (T&) unless T is void, in which case it is the same as (void*&) because...
Definition: refcount.hpp:434
bool empty() const
Returns the same as (!this->get()).
Definition: refcount.hpp:475
bool operator!=(rcptr const &rhs) const
Returns (this->m_ptr != rhs.m_ptr).
Definition: refcount.hpp:366
size_t ref_count() const
Returns the number of references to this object's pointer, or zero if no pointer is bound.
Definition: refcount.hpp:465
bool operator<(rcptr const &rhs) const
Returns this->get() < rhs.get().
Definition: refcount.hpp:375
The refcount namespace encapsulates code for a reference-counted smart pointer.
Definition: refcount.hpp:26
void *& type
Same as (void*&).
Definition: refcount.hpp:90
static type deref(type xx)
Returns xx.
Definition: refcount.hpp:92
Internal detail for dereferencing pointers.
Definition: refcount.hpp:77
static type deref(T *t)
Returns *t.
Definition: refcount.hpp:81
T & type
Same as (T&).
Definition: refcount.hpp:79
A no-op "destructor" for use with rcptr.
Definition: refcount.hpp:33
void operator()(T *&t)
Assigs t to 0 without deleting t.
Definition: refcount.hpp:36
The default destructor/cleanup functor for use with rcptr<>.
Definition: refcount.hpp:47
void operator()(T *&t)
Calls delete t and assigns t to 0.
Definition: refcount.hpp:58