Boost Smart Pointer Types and Usage

Smart Pointers

Couple of weeks ago I’ve made a presentation for one of the leading high-tech companies in Israel. In this presentation I’ve reviewed the Boost libraries. One of the libraries I’ve presented was the Smart Pointer library. I was quite surprised that software developers are not acquainted with the smart pointers. To help them I’ve prepared a summary document explaining how to use the smart pointer based on the Boost as an example. I think this memo can help other programmers, so I’ve decided to publish it in our blog.

Boost Smart Pointers

It seems that every programmer at least once in his practice encountered with pointer related problems: memory leaks, memory corruptions, access violations, etc. When the memory leak is detected in late stages of a system development, finding and fixing it becomes a nightmare. In modern platforms such as Java and Microsoft .Net garbage collection is taking care of most of the problems related to the memory management. But C++ programmers are still responsible for correcting memory management.

However there is a hope. The Boost library comes to the rescue. Along with other useful tools and libraries it contains implementation of smart pointers. The importance of this library can be seen in the fact that it is a part of proposed additions to the C++ standard (so called TR1). Moreover, several compiler vendors e.g. Microsoft VC++ 9.0 and gcc 4.1 partially implement this library.

In this article we want to show several usages of smart pointer library.

Lightweight smart pointers

We will start description of the smart pointers with the simplest and lightweight smart pointers called scoped_ptr. They are non-copyable and therefore cannot be passed as function arguments or be returned as function results. They are generally used for holding pointers in function scopes or as class fields. The classes using the scoped pointer are also non-copyable. Don’t worry, if you forget and you’ll try to pass the scoped pointer as the function parameter, you’ll get a compiler error, since its copy constructor and assignment operator are marked as private.

For example the code that uses regular pointers

class A
{
      int _i;

public:
      A(int i = 0) :_i(i) {}

      int get_i() { return _i; }
      void set_i(int i) { _i = i; }
};

void foo()
{
      A *pa = new A(5);
      int b = pa->get_i();

      // more code...
      delete pa;
}

The function foo() could be rewritten using scoped pointers as follows:

void foo()
{
      scoped_ptr<A> pa(new A(5));

      int b = pa->get_i();

      // more code...
}

Here you can see several peculiarities. First of all, you don’t need delete! When the scoped pointer goes out of the function scope its destructor is called. It in its turn releases the memory. Thus, when you use smart pointers you’ll never forget to release the memory! Secondly, the scoped pointers could be used just like the regular pointers. Thirdly, during initialization its constructor should be called explicitly. To use scoped pointers you should include this h-file #include <boost/scoped_ptr.hpp>

The immediate extension of the scoped_ptr is scoped_array, which is used for holding arrays and defined in <boost/scoped_array.hpp>. Mostly it behaves just like regular scoped pointers. But in addition it provides index operators for accessing array members and releases its memory using delete[] operator.

void bar()
{
      scoped_array<A> pa(new A[10]);

      // set array object values
      for (int j = 0; j < 10; j++)
      {
            pa[j].set_i(j);
      }

      // print array object values
      for (int j = 0; j < 10; j++)
      {
            cout << "pa[" << j << "] = " << pa[j].get_i() << endl;
      }
}

In this example an array of ten class A objects is used. As in case of scoped pointers, after the program leaves the function bar scope, the array is released.

The scoped pointers are very helpful when you deal with exceptions. Let’s see an example:

void bar()
{
      // throws an exception
}

void foo()
{
      A *pa = new A(5);

      bar();

      delete pa;
}

int main()
{
      try
      {
            foo();
      }
      catch (...)
      {
            // treat the exception
      }
}

In this case since foo()does not have a try-catch clause the code will never reach the delete statement and we will get a memory leak! The function foo() could be rewritten using smart pointer:

void foo()
{
      scoped_ptr<A> pa(new A(5));

      bar();
}

Here we won’t care about the exceptions. As it was noted above when the program leaves the function scope the smart pointer releases the pointer it holds, disregarding the way the scope was left.

The scoped pointers and arrays could be reset. This feature is used when you need to replace the old object pointed by the scoped pointer with the new one. The old object is silently released.

void foo()
{
      scoped_ptr<A> pa(new A(5));

      cout << "pa->i = " << pa->get_i() << endl;

      // more code...
      pa.reset(new A(20));

      cout << "pa->i = " << pa->get_i() << endl;
}

The output of this example will be

pa->i = 5
pa->i = 20

The object holding value 5 is released and replaces by an object holding the value 20.

Additional important feature of the smart pointers is an ability to swap values. This could be useful, for example, when your class holds and array and it should be enlarged

class B
{
      int _size;
      scoped_array<A> _array;

public:
      B(int size) : _size(size), _array(new A[size]) {}

      void enalarge()
      {
            // create a new array that is in twice
            // large than the existing one
            int newSize = _size * 2;
            scoped_array<A> tempArray(new A[newSize]);

            // copy the values of the existing array to the new one
            for (int i = 0; i < _size; i++)
            {
                  tempArray[i] = _array[i];
            }

            // update array size
            _size = newSize;

            // swap the arrays
            _array.swap(tempArray);
      }
};

In the function enlarge() a new temporary array of class A objects is created. The values of the existing array are copied to the new one. Then the member array and the temporary array are swapped: _pa points to the new larger array and tempArray now points to the old one. When function ends, the tempArray goes out of the scope and the memory it holds is released, leaving only the new array stored in the class member. And again we don’t need to deal with deletes.

Reference Counting Pointers

The next set of template classes provides the functionality that is generally expected of the smart pointers. These are the shared pointers implemented by shared_ptr and shared_array classes. They could be safely copied, passed as function parameters, returned as function results and so on. They use reference counter to manage the memory. Each time the shared pointer is being copied its reference counter is enlarged. When the shared pointer goes out of the scope the reference counter is decreased until it becomes zero, then the memory hold by the shared pointer is released.

Let’s see the example

void bar(shared_ptr<A> pa)
{
      pa->set_i(20);
}

void foo()
{
      shared_ptr<A> pa(new A(10));

      cout << "pa->i = " << pa->get_i() << endl;

      bar(pa);

      cout << "pa->i = " << pa->get_i() << endl;
}

At the beginning of function foo() and before entering to the function bar() the reference counter of pa equals to 1, immediately after the entering the reference counter becomes 2. Both smart pointers point to the same object and updates made in the function bar() are seen in the function foo(). When the bar() is left the reference counter decreased to 1 and upon exit from the foo() it’s being zeroed and the memory is being released.

All operations applied on scoped pointers like reset and swap are also applicable to shared pointers, the only difference that in corresponding situations the operations decrease the reference counter instead of releasing the memory.

void bar(shared_ptr<A> pa)
{
      pa.reset(new A(20));

      cout << "bar: pa->i = " << pa->get_i() << endl;
}

void foo()
{
      shared_ptr<A> pa(new A(10));

      cout << "foo: pa->i = " << pa->get_i() << endl;

      bar(pa);

      cout << "foo: pa->i = " << pa->get_i() << endl;
}

The output of this piece of code is:

foo: pa->i = 10
bar: pa->i = 20
foo: pa->i = 10

After the pa.reset() in the function bar() its pa variable points to the new memory object, which is released upon exit from the function bar(), but it does not influence the pa variable from the function foo(), since after the reset they point to the different memory objects and use different reference counters. As you can see the value of the object pointed by foo’s pa has not changed.

The shared pointers are not thread safe and when you use shared pointers in multi-threaded applications you should protect them from simultaneous access. There is no problem with simultaneous reading content of shared pointer, however, you should take additional measures when one of the threads writes (performs swap or reset operation) while the other one tries to read or write.

If you haven’t used smart pointers yet, start using it. I hope this short memo will help you with it.

 

No Comments »

No comments yet.

Leave a comment