Tag: memory management

  • Creating smart pointers in C++

    Introduction

    What are smart pointers? They are a means of handling the problems associated with normal pointers, namely memory management issues like memory leaks, double-deletions, dangling pointers etc. This post gives a simple guide to creating your own smart pointer in C++. As a simple starting example, consider a basic template class which can be used to hold generic data types:

    template <class T>
    class Ptr
    {
    public:
    	Ptr(T* d) { data = d;}	 
    
    private:
    	T* data;
    };
    

    And also consider an example class A which we will use the the smart pointer to hold:

    class A
    {
    public:
    	A() {}
    	~A() {}
    	void DoStuff() { std::cout << "Hello"; }
    };
    

    One often-encountered problem is that of forgetting to delete. Or maybe some exception gets thrown and the function is never given the chance to delete. Either way, the result is a memory leak, as would be the case in the following function since the pointer is never deleted:

    void function()
    {	
    	Ptr<A> p( new A() );		
    	// memory leak!
    }
    

    Note that each time function() is called, memory gets allocated for Ptr but is never deleted when the function goes out of scope. This kind of memory leak is precisely the kind of thing we wish to avoid. We need some mechanism that takes care of the task of deleting the pointer for us, lest we forget.

    Given that pointers do not have destructors, we can make our smart pointer class ‘smart’ by giving it one. Our Ptr class will not only hold the pointer, but delete it when the destructor is called. We no longer have to worry about deleting Ptr when it goes out of scope:

    template <class T>
    class Ptr
    {
    public:
    	Ptr(T* d) { data = d;}	
    	~Ptr() { delete data; }  
    
    private:
    	T* data;
    };
    

    Try running function again, to demonstrate that this actually happens. (You may wish to insert a breakpoint in the Ptr destructor to verify this.)

    void function()
    {	
    	Ptr<A> p( new A() );		
    	// pointer deleted when we go out of scope...
    }
    

    Creating the interface for the smart pointer

    Since a smart pointer is meant to look like and behave like a smart pointer without actually being a pointer, it should support the same kinds of interfaces as pointers such as the dereferencing (‘*‘) and indirection (‘->‘) operators. This is what the updated Ptr class looks like with the overloaded ‘*‘ and ‘->‘ operators added:

    template <class T>
    class Ptr
    {
    public:
    	Ptr(T* d) { data = d;}	
    	~Ptr() { delete data; }	
    
    	T* operator->() { return data; }
    	T& operator*() { return *data; }
    
    private:
    	T* data;
    };
    

    For example, we can now use the overloaded ‘->‘ (indirection) operator to access the DoStuff method in class A, so that it now looks like a standard pointer operation (without actually being one):

    void function()
    {	
    	Ptr<A> p( new A() );
    	p->DoStuff();	
    	// pointer deleted when we go out of scope...
    }
    

    Using Reference Counting

    There is however, one as yet unanticipated danger of deleting the pointer more than once. Try the following code segment to see what I mean:

    void function()
    {	
    	Ptr<A> p( new A() );
    	p->DoStuff();	
    
    	Ptr<A> q = p;
    	// Danger! pointer double-deleted...
    }
    

    In here p is assigned to q, and both refer to the same class A pointer. As soon as we go out of scope the destructor of Ptr is called twice, the first of which is successful, while the second fails, since p and q are referring to the same pointer. The same thing would happen even if we were using normal pointers.

    The way around this is to use reference counters to track of the pointers we have added or deleted, and add it to our smart pointer class. We maintain a pointer to the reference counter class in our smart pointer class and this pointer is shared for all instances of the smart pointer that refers to the same pointer. To implement this we must also maintain an assignment operator and copy constructor in our smart pointer class:

    class ReferenceCounter
    {
    private:
    	int count; 
    
    public:
        void Add() { count++; }
    
    	// Decrement and release reference count
        int Release() { return --count; }
    };
    
    template <class T>
    class Ptr
    {
    public:
    
    	Ptr() : data( 0 ), ref( 0 ) 
    	{		
            ref = new ReferenceCounter();        
            ref->Add();
    	}
    
    	Ptr(T* d) : data(d), ref(0)
    	{ 
    		ref = new ReferenceCounter();        
            ref->Add();
    	}		
    
    	// Copy constructor
        Ptr(const Ptr<T>& ptr) : data(ptr.data), ref(ptr.ref)
        {
            // Copy constructor
            // Copy the data and reference pointer
            // and increment the reference count
            ref->Add();
        }
    
    	// Assignment operator
    	Ptr<T>& operator = (const Ptr<T>& ptr)
        {
            // Assignment operator
            if (this != &ptr) // Avoid self assignment
            {
                // Decrement the old reference count
                // if reference become zero delete the old data
                if(ref->Release() == 0)
                {
                    delete data;
                    delete ref;
                }
    
                // Copy the data and reference pointer
                // and increment the reference count
                data = ptr.data;
                ref = ptr.ref;
                ref->Add();
            }
            return *this;
        }
    
    	~Ptr()
    	{ 
    		// Only when ref. count reaches 0 delete data
    		if(ref->Release() == 0)
            {
                delete data;
                delete ref;
            }
    	}	
    
    	T* operator->() { return data; }
    	T& operator*() { return *data; }
    
    private:
    	T* data;
    	ReferenceCounter* ref;
    };
    

    Now lets try it in a situation whereby the destructor is called multiple times:

    void function()
    {	
    	Ptr<A> p( new A() );
    	p->DoStuff();
    	{
    		Ptr<A> q = p;
    		q->DoStuff();
    		// Destructor of q called here..
    
    		Ptr<A> r;
            r = p;
            r->DoStuff();
            // Destructor of r called here..
    	}
    	p->DoStuff();
        // Destructor of p will be called here 
    }
    

    This is what happens step-by-step: we create a smart pointer p of type A. The constructor of Ptr is called, the data is stored, and a new ReferenceCounter pointer is created . The Add method of ReferenceCounter increments the reference count from 0 to 1.

    The line Ptr q = p; creates a new smart pointer q using the copy constructor and the data is copied and the reference further incremented to 2 .

    The line r = p; assigns the value of p to q using the asignment operator. The data is also copied and the reference count further incremented to 3. When r and q go out of scope, (when we leave the inner curly brackets) the destructors of the respective objects are called.

    The reference count is decremented, but the data is not deleted until the reference count is zero. This happens only when the destructor of p is called. Hence our data is deleted only when nothing is referring to it.

    Here is the complete code listing:

    #include <iostream>
    
    class A
    {
    public:
    	A() {}
    	~A() {}
    	void DoStuff() { std::cout << "Hello"; }
    };
    
    class ReferenceCounter
    {
    private:
    	int count; 
    
    public:
        void Add() 
    	{ 
    		count++; 
    	}
    
    	// Decrement and release reference count
        int Release()
    	{ 
    		return --count; 
    	}
    };
    
    template <class T>
    class Ptr
    {
    public:
    
    	Ptr() : data( 0 ), ref( 0 ) 
    	{		
            ref = new ReferenceCounter();        
            ref->Add();
    	}
    
    	Ptr(T* d) : data(d), ref(0)
    	{ 
    		ref = new ReferenceCounter();        
            ref->Add();
    	}		
    
    	// Copy constructor
        Ptr(const Ptr<T>& ptr) : data(ptr.data), ref(ptr.ref)
        {
            // Copy constructor
            // Copy the data and reference pointer
            // and increment the reference count
            ref->Add();
        }
    
    	// Assignment operator
    	Ptr<T>& operator = (const Ptr<T>& ptr)
        {
            // Assignment operator
            if (this != &ptr) // Avoid self assignment
            {
                // Decrement the old reference count
                // if reference become zero delete the old data
                if(ref->Release() == 0)
                {
                    delete data;
                    delete ref;
                }
    
                // Copy the data and reference pointer
                // and increment the reference count
                data = ptr.data;
                ref = ptr.ref;
                ref->Add();
            }
            return *this;
        }
    
    	~Ptr()
    	{ 
    		// Only when ref. count reaches 0 delete data
    		if(ref->Release() == 0)
            {
                delete data;
                delete ref;
            }
    	}	
    
    	T* operator->() { return data; }
    	T& operator*() { return *data; }
    
    private:
    	T* data;
    	ReferenceCounter* ref;
    };
    
    void function()
    {	
    	Ptr<A> p( new A() );
    	p->DoStuff();
    	{
    		Ptr<A> q = p;
    		q->DoStuff();
    		// Destructor of q called here..
    
    		Ptr<A> r;
            r = p;
            r->DoStuff();
            // Destructor of r called here..
    	}
    	p->DoStuff();
        // Destructor of p will be called here 
    }
    
    int main()
    {
    	function();	
    	return 0;
    }
    
  • The Big Three in C++

    Constructor, destructors and assignment operators

    There is a rule of thumb in C++ that if a class defines a destructor, constructor and copy assignment operator – then it should explicitly define these and not rely on their default implementation.

    Why do we need them?

    In the example below, the absence of an explicit copy constructor will simply make an exact copy of the class and you end up with two classes pointing to the same memory address – not what you want.  When you delete the array pointer in one class for example, the other class will be pointing to memory to which you have no idea as what it contains.

    Consider the following example class which is used to house an array of integers plus its size. It is written in such a way as to guarantee a crash:

    #include ,algorithm>
    
    class Array
    {
    private:
        int size;
        int* vals;  
    
    public:
        ~Array();
        Array( int s, int* v );
    };
    
    Array::~Array()
    {
       delete vals;
       vals = NULL;
    }
    
    Array::Array( int s, int* v )
    {
        size = s;
        vals = new int[ size ];
        std::copy( v, v + size, vals );
    }
    
    int main()
    {
       int vals[ 4 ] = { 1, 2, 3, 4 };
    
       Array a1( 4, vals );
       Array a2( a1 );
       return 0;
    }
    // Bam!
    

    Once the program goes out of scope (exits the main loop), the class destructor is called. Twice. Once for Array object a1 and then for a2. Remember that the compiler-generated default copy constructor simply makes a copy of the pointer vals.  It does not know that it also needs to allocate memory for a new vals.  When a1 is deleted, its destructor frees up vals.  Subsequent use of vals in the other instance a2 when once more trying to delete it in the destructor causes the program to fall over, since that instance of vals does not exist any more.

    Using explicitly defined copy constructors

    There is clearly a need to explicitly define a copy constructor that correctly copies vals.  Suppose someone has now defined an acceptable copy constructor, in addition to the default constructor:

    class Array
    {
    private:
       int size;
       int* vals;  
    
    public:
       ~Array();
       Array( int s, int* v );
       Array( const Array& a );
    };
    
    Array::Array( const Array &a )
    {
       size = a.size;
       vals = new int[ a.size ];
       std::copy( a.vals, a.vals + size, vals );
    }
    

    This improves the situation in that the code will not crash when exiting the main loop. But we’re not in the clear yet. It is still vulnerable to a crash as soon as someone has a go at assigning an instance of Array.

    Now try this:

    int main()
    {
       int vals[ 4 ] = { 1, 2, 3, 4 };
       Array a1( 4, vals );
       Array a2( a1 );
       a1 = a2;
       return 0;
    }
    // Bam!
    

    As before two instances of vals are allocated, and the two then get deleted just fine. So why the crash again?  As before, both instances of Array are pointing to the same instance of vals.  This is due to the default assignment operator that gets called, unless one is explicitly defined.  It only knows how to assign the pointer to vals.  Consequently you also need to explicitly define an assignment operator to complement the copy constructor. Full code listing as follows.

    The full picture: copy constructors and assignment operator

    #include <algorithm>
    
    class Array
    {
    private:
       int size;
       int* vals;
    
    public:
       ~Array();
       Array( int s, int* v );
       Array( const Array& a );
       Array& operator=( const Array& a );
    };
    
    Array::~Array()
    {
       delete vals;
       vals = NULL;
    }
    
    Array::Array( int s, int* v )
    {
       size = s;
       vals = new int[ size ];
       std::copy( v, v + size, vals );
    }
    
    Array::Array( const Array &a )
    {
       size = a.size;
       vals = new int[ a.size ];
       std::copy( a.vals, a.vals + size, vals );
    }
    
    Array& Array::operator =(const Array &a)
    {
       if ( &a != this )
       {
          size = a.size;
          vals = new int[ a.size ];
          std::copy( a.vals, a.vals + size, vals );
       }
       return *this;
    }
    
    int main()
    {
       int vals[ 4 ] = { 1, 2, 3, 4 };
       Array a1( 4, vals );
       Array a2( a1 );
       a1 = a2;
       return 0;
    }
    
  • Avoiding Memory Leaks using Boost Libraries

    Using boost::scoped_array

    When we want to dynamically allocate an array of objects for some purpose, the C++ programming language offers us the new and delete operators that are intended to replace the traditional malloc() and free() subroutines that are part of the standard library : (more…)

  • Using Smart Pointers to Avoid Memory Leaks

    Using boost::scoped_array

    When we want to dynamically allocate an array of objects for some purpose, the C++ programming language offers us the new and delete operators that are intended to replace the traditional malloc() and free() subroutines that are part of the standard library :
    (more…)