C++11: Smart Pointers, part 2: unique_ptr

This is the second post 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

C++11 ships with a set of out-of-the-box smart pointers that help us to manage the memory easily.

One of those smart pointers is the unique_ptr.

Consider this piece of code:

#include <iostream>
#include <ctime> 

using namespace std;

class A
{
  public:
    A() { cout << "ctor invoked" << endl; }
    virtual ~A() { cout << "dtor invoked" << endl; }
    void sayHi() const { cout << "HI" << endl; }
};

class B : public A { };

void test()
{
	if (clock() % 5 == 0)
		throw std::exception();
}

int main()
{
  A* a = new A();
  A* b = new B();

  clock_t clk = clock();
  if (clk % 2 == 0)
    return -1;

  test();

  a->sayHi();
  
  delete b;
  delete a;
}

If you execute this code several times, you will get three types of output:

This one:

ctor invoked
ctor invoked
HI
dtor invoked
dtor invoked

Or this, that occurs when the number returned by clock() is even:

ctor invoked
ctor invoked

Or this, that occurs when the exception in test() is thrown:

ctor invoked
ctor invoked
terminate called after throwing an instance of 'std::exception'
what(): std::exception
Abort trap: 6

As you see, the expected behavior just occurs in the first case, in the other ones (with a premature return or when an exception is thrown), we are not invoking delete, so we are leaving memory leaks.

What would be the perfect solution? Using stack variables, but that is not always possible: Maybe we invoke a method that returns a pointer to something, or returns a pointer of a base class to be used “polymorphically” or we need to rely on polymorphism (i.e. in virtual functions).

The ugly solution? Patching our code:

int main()
{
  A* a = new A();
  A* b = new B();

  clock_t clk = clock();
  if (clk % 2 == 0)
  {
    delete a;
    delete b;
    return -1;
  }

  try
  {
    test();
  }
  catch (const std::exception& ex)
  {
    cerr << "An expected error occurred" << endl;
    delete a;
    delete b;
    return -2;
  }

  a->sayHi();
  
  delete b;
  delete a;
}

Cons:

  • Ugly
  • A lot of error handling code
  • Hard to maintain, a lot of duplicate code

The nice way? Using unique_ptr.

unique_ptr is a smart pointer that invokes automatically the destructor of the pointee object that is wrapping, when it reaches the end of its lifetime.

That mechanism is useful in these scenarios:

  • You rely on objects allocated in the freestore, but you want to have them alive just in the method you are using them.
  • You have variable members of your class declared as pointers to objects allocated in the freestore. Using unique_ptr instead of raw pointers, avoid you to invoke explicitly their destructors in the destructor of your class AND ensures your objects will be properly released if some exception occurs while constructing the objects of your class.

So, the code I implemented above would look like this using unique_ptr:

#include <ctime> 
#include <iostream>
#include <memory>

using namespace std;

class A
{
  public:
    A() { cout << "ctor invoked" << endl; }
    virtual ~A() { cout << "dtor invoked" << endl; }
    void sayHi() const { cout << "HI" << endl; }
};

class B : public A { };

void test()
{
	if (clock() % 5 == 0)
		throw std::exception();
}

int main()
{
  unique_ptr<A> a(new A { });
  unique_ptr<A> b(new B { });

  clock_t clk = clock();
  if (clk % 2 == 0)
  {
    return -1;
  }

  try
  {
    test();
  }
  catch (const std::exception& ex)
  {
    cerr << "An expected error occurred" << endl;
    return -2;
  }

  a->sayHi();
}

To get the raw pointer of the pointee object inside this smart pointer, the unique_ptr class template implements a method called .get(). This method should be only used in case you are using old code and you need to pass it raw pointers instead.

unique_ptr deletes the copy constructor and the assignment operator but provides the move constructor and the move assignment operator to transfer the pointee to other unique_ptr instance.

Look at this piece of code:

#include <iostream>
#include <memory>

using namespace std;

class X
{
    int x;
  public:
    X(int x) : x(x) { cout << "ctor invoked" << endl; }
    ~X() { cout << "dtor invoked" << endl; }
    void sayHi() const { cout << "HI " << x << endl; }
};

int main()
{
  unique_ptr<X> a(new X { 2 });
  // unique_ptr<X> c = a; //does not compile! no copy constructor
  unique_ptr<X> b = std::move(a); //valid: move constructor
  cout << a.get() << endl;
  b->sayHi();
}

If you uncomment the second line of code in the main function, your program will not compile because the copy constructor has been explicitly removed. Instead of it, the move constructor is in place. This move constructor “steals” the pointer from a and sets it to b. As you see, when a.get() is executed, it returns a null pointer (because the pointer was moved to b).

Why is that behavior good? Because in that way the smart pointer ensure us that there is just one owner of the pointed object.

unique_ptr is the C++11 replacement to the old auto_ptr that has similar behavior, but that was deprecated because it does not support move semantics (and implements a “hack” in its copy constructor that moves the pointer to the target object (what is not legal for a copy constructor).

I wrote some more details on unique_ptr in this blog post:

C++: Smart pointers, part 3: More on unique_ptr

Advertisements

3 thoughts on “C++11: Smart Pointers, part 2: unique_ptr

  1. Very nice examples.

    I would only change the invocation of the constructors to instead using the uniform initialization syntax. Not only is the risk for “the most vexing parse” (http://en.wikipedia.org/wiki/Most_vexing_parse) removed but I think it is clearer to the user as well.

    I.e. using these types of syntax instead.

    unique_ptr<X> a{  new X() };   // or
    
    unique_ptr<X> b(  new X   );   // or
    
    unique_ptr<X> c{  new X{} };  // this is probably the best 
    
      
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s