Sunday, October 7, 2012

Reference counted classe in C++

My inspiration for Smart pointers and reference counted classes come from Webkit, time when i started hacking into webkit source code :)

Although i have tried to remain much simple and easier than in original webkit implementation of reference counted classes, but you will see most of code here resembles with webkit.

The idea of reference counted is very simple, every object  maintains a count(or we can say ref count) of number of pointers pointing towards itself, and as long as the count remains greater than zero, the object is valid and the moment its count becomes zero it is automatically deleted.
The object implemented  a pair of methods ref() and deref(), ref() method increments its count by one and similarly deref() decrements its count by one. Every call to ref() must be balanced by a call to deref().
Every time a new smart pointer points towards the object its ref count is incremented by one(via ref() method), and every time a smart pointer is destroyed the object ref count is decremented by one(via deref() method).
Ref counted objects must be wrapped inside a special smart pointer(RefPtr) in order to properly use and take benefit from reference count implementation.

Let us first talk about RefCounted class and then we will talk about RefPtr.

RefCounted is simply a template class, which has only a single instance member, i.e m_refCount which is the count i have talked above.


template <typename T> class RefCounted {
public:
    virtual ~RefCounted()
    {
    }
    void ref()
    {
        ++m_refCount;
    }
    bool deref()
    {
        if (m_refCount == 1)
            return true;
        
        --m_refCount;
        return false;
    }
protected:
    RefCounted()
    : m_refCount(0)
    {
    }
private:
    // Making it non-copyable,so that it cannot be used as POD
    RefCounted(const RefCounted&)
    {
    }
    RefCounted& operator=(const RefCounted&)
    {
    }
    
    unsigned int m_refCount;
};


Fairly straight forward implementation.

Now let us see RefPtr class implementation

template<typename T> class RefPtr {
public:
    RefPtr()
    : m_ptr(0)
    {
    }
    RefPtr(T* ptr)
    : m_ptr(ptr)
    {
        checkAndRef(m_ptr);
    }
    RefPtr(const RefPtr& o)
    : m_ptr(o.m_ptr)
    {
        checkAndRef(m_ptr);
    }
    template <typename U> RefPtr(const RefPtr<U>& o)
    : m_ptr(o.get())
    {
        checkAndRef(m_ptr);
    }
    ~RefPtr()
    {
        checkAndDeref(m_ptr);
    }
    T& operator*() const
    {
        return *m_ptr;
    }
    T* operator->() const
    {
        return m_ptr;
    }
    T* get() const
    {
        return m_ptr;
    }
    
    RefPtr& operator=(const RefPtr& o);
    RefPtr& operator=(T* optr);
    
    template<typename U> RefPtr& operator=(const RefPtr<U>&);
    
    void clear();
    void swap(RefPtr&);
    
private:
    T* m_ptr;
};


template<typename T> inline RefPtr<T>& RefPtr<T>::operator=(const RefPtr<T>& o)
{
    T* optr = o.get();
    checkAndRef(optr);
    T* ptr = m_ptr;
    m_ptr = optr;
    checkAndDeref(ptr);
    return *this;
}
template<typename T> inline RefPtr<T>& RefPtr<T>::operator=(T* optr)
{
    checkAndRef(optr);
    T* ptr = m_ptr;
    m_ptr = optr;
    checkAndDeref(ptr);
    return *this;
}
template<typename T> template<typename U> inline RefPtr<T>& RefPtr<T>::operator=(const RefPtr<U>& o)
{
    T* optr = o.get();
    checkAndRef(optr);
    T* ptr = m_ptr;
    m_ptr = optr;
    checkAndDeref(ptr);
    return *this;
}


Every time RefPtr takes in a new object it calls ref() on it through checkAndRef() and when RefPtr is destroyed it calls deref() through checkAndDeref() in its destructor. Rest of methods are for general usability.

In this class two important functions are used checkAndRef() and checkAndDeref(), here is the implementation of these functions

template<typename T> void checkAndRef(T* ptr)
{
    if (ptr != 0)
        ptr->ref();
}
template<typename T> void checkAndDeref(T* ptr)
{
    if (ptr != 0)
        if (ptr->deref())
            delete ptr;
}

In order to use RefCounted we must inherit from it.

Let us have a class "Foo" and we want to make it reference counted class. So the first step is to inherit "Foo" from "RefCounted"


class Foo : public RefCounted<Foo> {
public:
    static RefPtr<Foo> create()
    {
        RefPtr<Foo> foo(new Foo());
        return foo;
    }
    virtual ~Foo()
    {
    }
    // Rest of Foo public implementation
private:
    Foo()
    {
    }
    
    // Making it non-copyable,so that it cannot be used as POD
    Foo(const Foo& o);
    Foo& operator=(const Foo& o);
    
    // Rest of Foo private implementation
};


We have provided a public static factory method for creation of Foo instance and make its constructor private, in this we have make it sure the object is always created and wrapped inside RefPtr before returning and it is automatically deleted upon destruction.
Also we have made this class non-copyable so that it cannot be used as POD(Plain Old Datatype).

In main() method(or any other place), we may use the whole implementation of RefCounted, RefPtr as follows


void funA(RefPtr<Foo> arg)
{
    // do whatever want to do with arg
}
int main(int argc, const char * argv[])
{
    RefPtr<Foo> obj = Foo::create();
    funA(obj);
    
    return 0;
}


Now let us examine in detail what happens to m_refCount, during whole execution of this code block.

  1. First static create() method is called.
  2. Inside create() and instance of Foo is created through "new" operator, and ref count is zero here.
  3. The newly created object is immediately wrapped inside RefPtr(foo), and its ref() method is called which increments its ref count by one, now its ref count is one.
  4. Returned object is created(it is an anonymous object), which calls ref() and its ref count goes to two.
  5. Local smart pointer object "foo" is destroyed when control returns from "create()" method which calls deref() and ref count goes to one.
  6. "obj" is crated which calls ref() and ref count goes to two.
  7. Returned anonymous object is destroyed which calls deref() and ref count is one.
  8. Argument object(arg) is created (to pass to function funA()) which calls ref() and ref count goes to two.
  9. When control returns from funA() the "arg" object is destroyed which calls deref() and ref count goes to one.
  10. When control returns from main() the local object "obj" is destroyed and underlaying wrapped "Foo" object is deleted.
On some compilers the during optimization stage some of above steps may be optimized away.


Issues

  • The above implementation of RefPtr will introduce reference count churn, it is even more if same object is passed as argument to a method/function and it returns the same object. One possible solution is to use another smart pointer namely PassRefPtr(more on this in my next blog when i talk about object ownership).
  • Above implementation RefCounted is not thread safe, it is potentially dangerous to pass a RefCounted object to other thread or return it from other thread. It may corrupt our m_refCount instance variable and race conditions may arise. 

Complete files can be downloaded from RefCounted.h, RefPtr.h & Foo.h

No comments:

Post a Comment