Monday, June 22, 2009

boost intrusive_ptr

boost::intrusive_ptr is one of the six smart pointer class templates provided by boost. It is interesting in that it is widely used as shared_ptr, but sometiems very useful. It relies on the stored object to provide its own reference counting semantics using two functions that must be called: intrusive_ptr_add_ref and intrusive_ptr_release. These functions take a pointer argument to the pointer that is to be managed and must be defined in the boost namespace, or if the compiler supports Koenig lookup, in the namespace where the arguments are defined.

Read more about intrusive_ptr here: http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/intrusive_ptr.html

Here is a simple Example I put together for myself to understand how this works.

First, this is a base class that provides reference counting support by defining the intrusive_ptr_add_ref and intrusive_ptr_release. functions.

#include <ostream>
#include <boost/checked_delete.hpp>
#include <boost/detail/atomic_count.hpp>

template<class T>
struct intrusive_ptr_base
{
intrusive_ptr_base(): ref_count(0)
{
std::cout << " Default constructor " << std::endl;
}
//only construct an intrusive_ptr from another intrusive_ptr. That is it.
intrusive_ptr_base(intrusive_ptr_base<T> const&)
: ref_count(0)
{
std::cout << " Copy constructor..." << std::endl;
}

///does nto support assignment
intrusive_ptr_base& operator=(intrusive_ptr_base const& rhs)
{
std::cout << " Assignment operator..." << std::endl;
return *this;
}

friend void intrusive_ptr_add_ref(intrusive_ptr_base<T> const* s)
{
std::cout << " intrusive_ptr_add_ref..." << std::endl;
assert(s->ref_count >= 0);
assert(s != 0);
++s->ref_count;
}

friend void intrusive_ptr_release(intrusive_ptr_base<T> const* s)
{
std::cout << " intrusive_ptr_release..." << std::endl;
assert(s->ref_count > 0);
assert(s != 0);
if (--s->ref_count == 0)
boost::checked_delete(static_cast<T const*>(s));
}

boost::intrusive_ptr<T> self()
{
return boost::intrusive_ptr<T>((T*)this);
}

boost::intrusive_ptr<const T> self() const
{
return boost::intrusive_ptr<const T>((T const*)this);
}

int refcount() const
{
return ref_count;
}


private:
///should be modifiable even from const intrusive_ptr objects
mutable boost::detail::atomic_count ref_count;
};




By inheriting publicly from intrusive_ptr_base, we make available the essential reference counting functions: intrusive_ptr_add_ref and intrusive_ptr_release. Notice that we defined these arguments in the base class, so we defined these arguments int he namespace where the parameters are defined, and the best way to guarantee that the compiler finds them is to put them as friend functions in the base class. We put them as friends to give them access to the private data members. Notice that if we defined them as member functions, they will take the *this pointer and they won't match the expected signature that boost::intrusive_ptr requires. The output statements provided in the class are just for debugging purposes.

To test it:

#include <boost/intrusive_ptr.hpp>
#include "intrusive_ptr_base.hpp"

class Connection : public intrusive_ptr_base< Connection >
{
public:
Connection(int id, std::string tag)
: connection_id( id )
, connection_tag( tag ) {}

Connection(const Connection& rhs)
: connection_id( rhs.connection_id )
, connection_tag( rhs.connection_tag) {}

const Connection operator=( const Connection& rhs)
{
if(this != &rhs)
{
connection_id = rhs.connection_id;
connection_tag = rhs.connection_tag;
}
return *this;
}

private:
int connection_id;
std::string connection_tag;
};

int main()
{
std::cout << "Create an intrusive ptr" << std::endl;
boost::intrusive_ptr< Connection > con0 (new Connection(4, "sss") );
std::cout << "Create an intrusive ptr. Refcount = " << con0->refcount() << std::endl;
boost::intrusive_ptr< Connection > con1 (con0);
std::cout << "Create an intrusive ptr. Refcount = " << con1->refcount() << std::endl;
boost::intrusive_ptr< Connection > con2 = con0;
std::cout << "Create an intrusive ptr. Refcount = " << con2->refcount() << std::endl;

std::cout << "Create an intrusive ptr. Refcount = " << con2->refcount() << std::endl;
std::cout << "Destroy an intrusive ptr" << std::endl;
return 0;
}


We notice that when we construct an object or assign to it, boost::intrusive_ptr calls our functions to increment the reference count.

boost::intrusive_ptr has the same memory footprint as a raw ptr, and can be constructed from any T* raw pointer.

Also, shared_ptr if not used carefully can cause subtle crashes and bugs as I already discussed in this post here. That is, you should never create two shared_ptr objects using the same raw pointer, if you do, the destructor for the controlled resource will be called twice when the shared_ptr objects are destroyed. Instead, copy the first shared_ptr object to create a second shared_ptr object that controls the resource. To avoid it, always create a shared_ptr from another shared_ptr that points to that resource already. But if we use an intrusive_ptr, that problem goes away.

So use intrusive_ptr....

No comments: