The Enbridge Inc. "REF" C++ Reference Counting Pointer Framework

REF provides much of the utility of automatic Garbage Collection, without giving up the C/C++ pointer semantics and efficiency you are used to, and without the non-deterministic overhead associated with most GC schemes.

Unlike other C++ Reference Counting Pointer frameworks, REF:

One perceived drawback that ref::ptr's have relative to full-blown Garbage Collection, is that co-referencing objects (objects that retain references to each-other), can prevent auto-destruction. This is usually easily avoidable. If you have objects containing ref::ptr's to other objects, that eventually point back to the original object, just make sure that one object breaks the "loop" of linkage when no longer required; they will then auto-destruct as appropriate. Otherwise, ref::ptr's seem highly efficient compared to most GC algorithms, are very safe and easy to use, and yield virtually all of the simplicity benefits of GC, without sacrificing space- or time- determinism.

If your code must be very space- and time-efficient, or has determinism requirements that cannot be met with the GC schemes available to you, then REF is an excellent choice. The space/time efficiency tradeoff of using ref::ptr<T>/ref::ptr_tiny<T> ( 1 x sizeof( T * ), but deference requires 1 virtual method invocation) vs. ref::ptr_fast<T> ( 2 x sizeof( T * ), but dereference is inlined) is totally within your control (see ref/ref.H). Both the "small" and "fast" versions are interchangable and assignable.

How-To

Here is an example of 3 ref::ptr<int>'s, managing a couple of dynamically allocated int objects. In this example, since the new <int> objects don't have their own ref::counter built in, ref::ptr<int> allocates one (shared by all ref::ptr's sharing the object).


#include <ref>

ref::ptr<int>		a( new int );		// new int 'A' on heap, 'a' holds ref::counter of 1 to 'A'
ref::ptr<int>		b;			// 'b's ref::counter is 0, and it's pointer ( *b, b->, ...) is 0.
b	= new int;				// new int 'B' on heap; 'b's ref::counter is 1
ref::ptr<int>		c;
c 	= b;					// 'b' and 'c' share ref::counter of 2 to 'B'
a	= c;					// 'A' freed; 'a', 'b' and 'c' share ref::counter of 3 to 'B'

Basically, just ensure that any dynamically allocated <object> is immediately assigned to a ref::ptr<object>. Then, make sure you only assign it between other ref::ptr's. When the last ref::ptr referencing it is assigned something else, the object will auto-destruct. Use the ref::ptr<object> as you would an (object *).

If you have control over the definition of a class, avoid allocating a dynamic ref::counter for each new object of the class by adding a built-in ref::counter: just add ref::counter<your_class> as a(nother) base class, and one virtual method:


     class your_class 
         : public ref::counter<your_class> {
         virtual your_class *__ref_getptr()  { return this; }
         /* ... the rest of your_class's declaration ... */
     }

To add a ref::counter to a class that you do NOT have control of (named "object", for example), derive a new class <rcobject> from the base class <object> and also the ref::counter<rcobject> base class, and add one new virtual method (the ref::counter methods must be virtual, in order to maintain proper pointer conversion and auto-destruction). This new <rcobject> is trivially convertible to <object>, and has a built-in ref::counter, avoiding the additional dynamic allocation for each new <object>:


class object { /* ... */ ; };

// Make an "object" having its own ref::counter; ref::ptr will use it..
class rcobject
  : public object
  , public ref::counter<rcobject> {
    virtual rcobject *__ref_getptr()  { return this; } // dereference the object
    /* ... */
};
ref::ptr<rcobject>	r = new rcobject;

An alternative method for adding a ref::counter to "object" is to use the ref::dyn<object> template:


// Add reference-counting and self-destruction to some other object
ref::ptr<ref::dyn<object> > o = new ref::dyn<object>	

In both of these cases, the ref::ptr's will automatically detect and use the included ref::counter, instead of allocating one for each shared object. Use of ref::ptr's will be almost 100% as space-efficient as using a raw pointer to the object (one <int>, the reference counter, will be added to the size of the object)! No other dynamically or statically allocated objects of any kind are required for any housekeeping.

Other Implementations

The implementation of boost::shared_ptr<T> is comparable to ref::ptr_fast<T>. The size of each boost::share_ptr is 2 x sizeof( T * ), dereferencing is inlined (no virtual method invocations required), and a shared_counter is dynamically allocated for each group of shared_ptr<T>'s referencing a specific dynamically allocated T object.

The benefits of REF are several. First, the size of ref::ptr<T> is 1 x sizeof( T * ), at the cost of 1 virtual method invocation per dereference. So, if your application stores many, many pointers, then REF will cut your pointer storage space requirements by 50%. There is no equivalent in boost, that I am aware of.

Second, it appears that boost::shared_ptr always dynamically allocates a shared_counter object, whenever one (or more) smart_ptr's share a <T>. If your program creates many, many small T's, then the overhead of shared_counter allocated for each one will be significant (potentially doubling your dynamic memory requirements). Adding a ref::counter<T> as a base class of T will only increase the size of T by 1 integer and a virtual table pointer (the shared reference counter object). No other objects are then required for reference counting. Again, there appears to be no equivalent in boost.

Third, ref::ptr's are not thread-safe, and so do not have the overhead of thread-safety built into each reference increment/decrement. At first this may not seem to be a benefit, but I believe that it is. The details of transparently sharing dynamically allocated reference-counted storage between threads have proven too great for several widely supported implementations of std::string, produced by people much smarter than me, and all have been abandoned. Therefore, you may use ref::ptr's in multiple threads without protection, but do not assign or copy to/from them! Remember, copying one ref::ptr<T> to another involves a write to the shared reference counter, and destruction of the copy later involves another write! If you do copy, then all accesses to all copies must be encompassed by mutual exclusion intrinsics. In other words, treat them like any other shared object in the multi-threaded environment.

Examples

See the unit tests ref-test.C for many more examples. You can run the unit tests, and see their output here (the REF unit tests are implemented using CUT)

Define the preprocessor symbol REF_PTR_DEREF_FAST (see the GNU Make file GNUmakefile for an example ) to globally force ref::ptr<T> to implement ref::ptr_fast<T>, in applications where it is known in advance that speed is more important than space, all source code is available, and where compiled modules will never be shared outside of the compiled application.

Define the preprocessor symbol REF_PTR_COMPARE (see the GNU Make file GNUmakefile for an example ) to define functions that automatically compare ref::ptr<T> by their underlying pointers. This allows use of ref::ptr's as keys in sorted containers. If you wish to define such comparison methods yourself, you must put them in the namespace 'ref', or they will be hidden (probably by too many levels of template type deduction.)

Download

The latest version is always available in the ref-#.#.#.tgz file, here

Copyright (C) 2004,2005 Enbridge Inc.

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.