Peter Weinert is a scientific staff member of the Leibniz Supercomputing Centre in Munich, Germany.
This article presents a reference-counting base class template that is efficient and easy to apply for the smart pointer intrusive_ptr. Smart pointers encapsulate pointers to dynamically allocated resources such as heap memory or handles. They relieve you of explicit resource deallocation, therefore simplifying resource management, exception-safe programming, and the "Resource Acquisition Is Initialization" (RAII) idiom. Your tool box provides different smart pointer templates: auto_ptr, unique_ptr, shared_ptr, weak_ptr, or intrusive_ptr. This section sketches these template classes.
An Introduction to Common Smart Pointers
The auto_ptr class template provides a semantics of strict ownership of objects obtained via new ([C++03 §20.4.5]). Strict ownership means that no more than one auto_ptr object shall own the same object. The owned object is disposed using delete when the auto_ptr object itself is destroyed. Even though strict ownership precludes the CopyConstructible and the CopyAssignable requirements ([C++0x §20.2.1]), the copy constructor and the copy assignment operators are accessible. If an auto_ptr object is copied, ownership is transferred, thereby resetting the source. The copy and the source are not equivalent. This limits usability and may lead to incorrect and dangerous use. C++0x therefore deprecates auto_ptr and offers unique_ptr as an improved class template. The unique_ptr class template provides a semantics of strict ownership of objects ([C++0x §20.9.9]). Contrary to auto_ptr, strict ownership is constrained by syntax. The copy constructor and the copy assignment operator are inaccessible. Instead, unique_ptr meets the MoveConstructible and MoveAssignable requirements ([C++0x §20.2.1]) and move semantics are enabled in order to transfer ownership. Therefore, a function can explicitly receive or return ownership. A template parameter accepts a client-supplied deleter that becomes part of the type. Disposal is indirected to this deleter. unique_ptr can manage objects obtained using allocators other than new. Because of this additional flexibility, the memory footprint may be larger compared to auto_ptr.
The shared_ptr class template implements semantics of shared ownership ([C++0x §20.9.10.2], [BoostSmartPtr]). More than one shared_ptr object can own the same object, satisfying the CopyConstructible and the CopyAssignable requirements. A shared_ptr object typically maintains an internal reference counter. The make_shared and allocate_shared helper template functions may efficiently combine the allocation of the reference counter and the owned object, as most implementations allocate the reference counter dynamically. Overloaded constructors accept a client-supplied deleter as well, if disposal using delete is not appropriate. Thanks to clever type elimination, this deleter is not part of the type (contrary to unique_ptr). Once the reference counter drops to zero, the owned object is disposed.
Semantics of strict and shared ownership can lead to cycles (e.g. objects owning each other) where the owned objects will never be disposed. A weak_ptr object does not own the object and is used to break cycles of shared_ptr objects ([C++0x §20.9.10.3], [BoostSmartPtr]). As opposed to a raw pointer, weak_ptr observes its shared_ptr and therefore avoids dereferencing a dangling pointer.
The intrusive_ptr class template provides a semantics of shared ownership [BoostIntrusivePtr]. As this smart pointer uses intrusive reference counting, the owned object must provide the reference counter. intrusive_ptr is not directly aware of this counter. You supply free functions, intrusive_ptr_add_ref and intrusive_ptr_release, which solely manipulate the counter. The constructors and the destructor of intrusive_ptr invoke these functions through unqualified calls with a pointer to the reference-counted object as an argument. The object is not directly disposed by intrusive_ptr but by intrusive_ptr_release. To sum up, some requirements must be addressed when using intrusive_ptr:
- Semantics of intrusive_ptr_add_ref and intrusive_ptr_release
- Disposal of the object
- Visibility of intrusive_ptr_add_ref and intrusive_ptr_release
A Naive Approach
I develop a base class template, ref_counter, that is efficient and easy to apply with intrusive_ptr, and guarantees disposal using delete. As often, the devil is in the details. Let's start with a test case:
// test.cpp: A simple class foo using the base class ref_counter
#include <boost/intrusive_ptr.hpp>
#include "ref_counter.h"
namespace foo {
class foo: public intrusive::ref_counter {};
}
int main( )
{
boost::intrusive_ptr<foo::foo> p1(new foo::foo());
}
and get it running:
// ref_counter.h: A naive definition
#ifndef INTRUSIVE_REF_COUNTER_H_INCLUDED
#define INTRUSIVE_REF_COUNTER_H_INCLUDED
#include <boost/assert.hpp>
namespace intrusive {
class ref_counter
{
public:
friend void intrusive_ptr_add_ref(ref_counter* p)
{
BOOST_ASSERT(p);
++p->counter_;
}
friend void intrusive_ptr_release(ref_counter* p)
{
BOOST_ASSERT(p);
if (--p->counter_ == 0)
delete p;
}
protected:
ref_counter(): counter_(0) {}
virtual ~ref_counter() = 0 {}
private:
unsigned long counter_;
};
}
#endif
The class is fully exception safe, meeting the strongest guarantee, the no-throw guarantee (noexcept in C++0x). But have the requirements I mentioned above been met?
- Semantics: The counter (counter always refers to ref_counter::counter_) is initialized with zero, intrusive_ptr_add_ref increments the counter, and intrusive_ptr_release decrements the counter. Both free functions are friends, as they have to manipulate the encapsulated counter. In addition to the compiler-declared copy constructor and copy assignment operator, they provide the public interface of ref_counter. As the class is intended to be used as a base class, it is abstract and the constructor and destructor are placed in the protected section.
- Disposal: The object is disposed if the counter drops to zero. As it is deleted using a pointer to the base class, the destructor is virtual.
- Visibility: The intrusive_ptr_add_ref and intrusive_ptr_release functions reside in the same namespace scope as the base class ref_counter. They do not pollute the global namespace. In fact, their names are not even visible during an ordinary lookup [C++03 §14.6.5-2], because they are in the lexical scope of ref_counter. Their names are found through argument-dependent lookup (ADL, Koenig lookup [C++03 §3.4.2-2]).
Getting It Right
Did you spot the bug? Do the two free functions solely manipulate the counter? Think about compiler-generated definitions in ref_counter:
p1 = new foo::foo(*p1); // copy construction of foo
This code allocates a new foo-object using the copy constructor of foo, which uses the implicitly defined copy constructor of its ref_counter base class ([C++03 §12.8-8]), which copies the non-zero counter. But as a new foo-object is constructed, the counter should start over with zero. Remember, only the two free functions should manipulate the counter. This happens in the assignment operator of intrusive_ptr, where the old foo object is detached and the new one is attached (indirectly calling intrusive_ptr_release and intrusive_ptr_add_ref) using the "copy construct a temporary and swap" idiom. The copy assignment operator of ref_counter is another suspicious candidate:
*p1 = foo::foo(); // copy assignment of foo
The implicitly defined copy assignment operator performs memberwise assignment of its subobjects ([C++03] §12.8-13). Therefore, the implicitly defined copy assignment of ref_counter is called. It overwrites the left counter with the right (overwritten with zero in this case). Do not get confused: The intrusive_ptr object is not assigned to or changed and so is its pointer member, but the object pointed to is assigned to. The number of referencing pointers did not change. Therefore, the assignment operator of ref_counter must not alter the counter, but do nothing. Because ref_counter is an abstract base class, the missing explicit definitions are placed in the protected section:
// <b>ref_counter</b>.h: Adding copy constructor and assignment operator
class ref_counter
{
// …
protected:
ref_counter(const ref_counter&) : counter_(0) {}
ref_counter& operator=(const ref_counter&) { return *this; }
};
Always keep assignment and copy construction in mind, especially if the class is managing a resource. In addition, the free std::swap function works correctly with classes derived from ref_counter, such as in std::swap(*p1, foo::foo()). Swapping ref_counter does nothing, and a swap-member function looks like:
void ref_counter::swap(ref_counter&) {} // noexcept


