Channels ▼
RSS

C/C++

A Base Class for Intrusively Reference-Counted Objects in C++



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], [Boost­SmartPtr]). 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, intru­sive_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


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video