C++: Smart pointers, part 1

This is the first of several posts I wrote related to smart pointers:

  1. Smart pointers
  2. unique_ptr
  3. More on unique_ptr
  4. shared_ptr
  5. weak_ptr

Memory management in C is error-prone because keeping track of every block of memory allocated and deallocated can be confusing and stressful.

Although C++ has the same manual memory management as C, it provides additional features that make memory management easier:

  • When an object is instantiated on the stack (e.g., Object o;), the C++ runtime ensures that the object’s destructor is invoked when the object goes out of scope (when the end of the enclosing block is reached, a premature ‘return’ is encountered, or an exception is thrown), thereby releasing all memory and resources allocated for that object. This very nice feature is called RAII.
  • (Ab)using the feature of operator overloading, we can create classes that simulate pointer behaviour. These classes are called: Smart pointers.

So, smart pointers are classes (typically implemented as class templates, to make them highly reusable) that encapsule a pointer and mimic its behaviour, but implement also some policies to release the objects “automatically”. The “magic” of smart pointers lies in the fact that they are always defined as stack variables, thus, the invocation of their destructor is guaranteed.

Take a look at this code snippet:

#include <iostream>

class A
{
  private:
    int val;
  public:
    A(int val) : val(val) { std::cout << "A ctor" << std::endl; }
    ~A() { std::cout << "A dtor" << std::endl; }
    int get_value() const { return val; }
};

int main()
{
  std::cout << "Enter a number: " << std::endl;
  int n;
  std::cin >> n;

  A* a = new A(n);
  if (n == 2)
    return 0;

  std::cout << "Value entered: " << a->get_value() << std::endl;
  delete a;
  return 0;
}

The code shown above asks the user to enter a number, the user enters it and if the
number is 2, the program finishes, otherwise, the program shows the number entered before exiting. If you look into the code you will realize that if the value entered equals 2, the program finishes WITHOUT calling the destructor of A. Though my example is quite trivial, forgetting to call the destructors when returning in several points in our code is one of the most common sources of memory leaks.

Smart pointers are good candidates in this point; we can write a smart pointer class template that releases the object when the smart pointer gets out of scope. To simulate the behavior of plain-old-pointers, the operator* and operator-> must be overloaded: Look into this piece of code implementing my smart pointer:

template <T>
class ptr
{
  private:
    T* pointee;

  public:
    explicit ptr(T* pointee) : pointee(pointee) { }
    ~ptr() { delete pointee; }

    T* operator->() { return pointee; }
    const T* operator->() const { return pointee; }

    T& operator*() { return *pointee; }
    const T& operator*() const { return *pointee; }
};

And now look into a modified main() function that uses the smart pointer instead of the normal one:

int main()
{
  std::cout << "Enter a number: " << std::endl;
  int n;
  std::cin >> n;

  ptr<A> a(new A(n));
  if (n == 2)
    return 0;

  std::cout << "Value entered: " << a->get_value() << std::endl;
  return 0;
}

My variable a is defined as a smart pointer and will hold the pointer to a new instance of A() allocated in the heap. As you can see, since the ptr<a> a declaration is a stack declaration, the C++ runtime guarantees that when the main() method will be finished (through the premature return of through the final one), the a destructor will be invoked and it will call the destructor of the pointee object.

A very similar policy was also implemented in the deprecated std::auto_ptr<T> and it exists in the C++11 std::unique_ptr<T> smart pointer.

Other approach of smart pointers is implementing a policy that will keep the track of how many smart pointers are pointing to the same object in the heap. Using reference counting, this kind of smart pointers call the destructor of the objects when the reference count has reached 0 (meaning that there are zero smart pointers pointing to such object in the heap).

A reference counter smart pointer can be implemented in a way similar to this one:

template <typename T>
struct refcntptrdata
{
  T* pointee;
  int refcnt;
};

template <typename T>
class refcntptr
{
  private:
    refcntptrdata<T>* data;

    void release()
    {
      data->refcnt--;
      if (data->refcnt == 0)
      {
        delete data->pointee;
        delete data;
      }
    }

  public:
    explicit refcntptr(T* pointee) : data(new refcntptrdata<T>())
    {
      data->pointee = pointee;
      data->refcnt = 1;
    }

    refcntptr(const refcntptr<T>& source) : data(source.data)
    {
      data->refcnt++;
    }

    refcntptr<T>& operator=(const refcntptr<T>& source)
    {
      release();
      data = source.data;
      data->refcnt++;
      return *this;
    }

    ~refcntptr()
    {
      release();
    }

    T* operator->() { return data->pointee; }
    const T* operator->() const { return data->pointee; }

    T& operator*() { return *(data->pointee); }
    const T& operator*() const { return *(data->pointee); }
};

As you can see in my implementation, the refcntptr<T> object just points to a refcntptrdata<T> object that is the one that keeps the pointer to the pointee and the reference counter.

The nice thing on this kind of pointers is that the copy constructor is very cheap because it just increments the reference counter.

C++11 has also a smart pointer implementing this policy: std::shared_ptr<T>

See how this smart pointer can be used in the example below:

int main()
{
  refcntptr<A> a(new A(2));
  refcntptr<A> b(new A(12));
  refcntptr<A> c = a;
  refcntptr<A> d = b;
  refcntptr<A> e(new A(5));
  e = b; // A=5 should be destroyed here because no one is using it anymore
  b = a;

  std::cout << "A: " << a->get_value() << std::endl;
  std::cout << "B: " << b->get_value() << std::endl;
  std::cout << "C: " << c->get_value() << std::endl;
  std::cout << "D: " << d->get_value() << std::endl;
  std::cout << "E: " << (*e).get_value() << std::endl;
}

To read more about smart pointers, visit these posts:

Smart Pointers, part 2: unique_ptr

2 thoughts on “C++: Smart pointers, part 1

  1. Thanks for your article. I would just like to point out a very small error. In the first bullet point you write

    “When an object is instantiated in the stack (e.g. Object o;); the C++ runtime ensure the destructor of such object is invoked when the object goes out of scope (the end of the method is reached, a premature ‘return’ is found or an exception is thrown);”

    Yes, the local object’s destructor is called “when the object goes out of scope .” This is correct.
    But your parenthesis is not quite right: “(The end of the method is reached … ”
    This should be “The end of the enclosing block, is reached”

Leave a comment