The Smart Pointer That Makes Your C++ Applications Safer – std::unique_ptr

Introduction

Due to its peculiar design and semantics, the standard smart pointer class auto_ptr was incompatible with other Standard Library components and was eventually deprecated. Shared_ptr was added to C++ programming in 2003 and has since replaced auto_ptr as the default smart pointer. However, it incurs noticeable performance overhead both in terms of size and speed. The C++0x Final Committee Draft (FCD) recently introduced another smart pointer called unique_ptr that combines the best of both worlds, it’s very efficient (even more than auto_ptr), and yet it’s fully compatible with the Standard Library, as is shared_ptr. That’s not all, did you know that unique_ptr supports arrays too?

Performance Issues

The first question you’d ask yourself is probably: “Why do I need to learn how to use another smart pointer class when shared_ptr fits the bill?” In one word: performance. Shared_ptr uses reference counting to allow the sharing of a single resource by multiple objects. Additionally, its destructor and other member functions are virtual. These properties facilitate the customization of shared_ptr. However, the size of a typical shared_ptr object is 40 bytes, which is 10 times bigger than the size of a raw pointer on a 32-bit platform. The presence of virtual member functions means that in many cases, calls to member functions are resolved dynamically, incurring additional runtime overhead. These issues may not concern you if you’re using shared_ptr sporadically. However, in time critical apps, or if you have containers that store a large number of shared_ptr objects, the performance overhead might be overwhelming. Unique_ptr gives you a safe and reliable smart pointer alternative that can compete with the size and speed of raw pointers. Here’s how it’s used.

Using Unique_ptr

Although unique_ptr is not a 100% source-compatible drop-in replacement for auto_ptr, everything you can do with auto_ptr, unique_ptr will do as well:

#include <utility>  //declarations of unique_ptr
using std::unique_ptr;
  // default construction
unique_ptr<int> up; //creates an empty object
  // initialize with an argument
unique_ptr<int> uptr (new int(3));
double *pd= new double;
unique_ptr<double> uptr2 (pd);
  // overloaded * and ->
*uptr2 = 23.5;
unique_ptr<std::string> ups (new std::string("hello"));
int len=ups->size();

Reset() releases the owned resource and optionally acquires a new resource:

unique_ptr<double> uptr2 (pd);
uptr2.reset(new double); //delete pd and acquire a new pointer
uptr2.reset(); //delete the pointer acquired by the previous reset() call

If you need to access the owned pointer directly use get():

void func(double*);
func(uptr.get());

Unique_ptr lets you install a custom deleter if necessary. A deleter is a callable entity (a function, function object etc.) that the smart pointer’s destructor will invoke to deallocate its resource. Unique_ptr's default deleter calls delete. If the resource isn’t an object allocated by new you’ll need to install a different deleter. For instance, a pointer allocated by malloc() requires a deleter that calls free():

#include <cstdlib>
int* p=(int*)malloc(sizeof(int));
unique_ptr<p, free> intptr; //initialized with a custom deleter
*intptr=5;

Swap() exchanges the resources and deleters of the two objects:

unique_ptr<double, std::free> up1 ((double*)malloc(sizeof(double));
unique_ptr<double> up2;
up2.swap(up1);
*up2=3.45;

Unique_ptr has implicit conversion to bool. This lets you use unique_ptr object in Boolean expressions such as this:

if (up1) //implicit conversion to bool
 cout<<*up1<<endl;
else
  cout<<"an empty smart pointer"<<endl;

Array Support

Unique_ptr can store arrays as well. A unique_ptr that owns an array defines an overloaded operator []. Obviously, the * and -> operators are not available. Additionally, the default deleter calls delete[] instead of delete:

unique_ptr<int[]> arrup (new int[5]);
arrup[0]=5;
cout<<*arrup<<endl; //error, operator * not defined
unique_ptr<char[], std::free> charup ((char*)( malloc(5));
charup[1]='b';

Compatibility with Containers and Algorithms

You can safely store unique_ptr in Standard Library containers and let algorithms manipulate sequences of unique_ptr objects. For containers and algorithms that move elements around internally instead of copying them around, unique_ptr will work flawlessly. If however the container or algorithm uses copy semantics for its value_type you’ll get a compilation error. Unlike with auto_ptr, there is no risk of a runtime crash due to a move operation disguised as copying:

vector > vi;
vi.push_back(unique_ptr(new int(0)));  // populate vector
vi.push_back(unique_ptr(new int(3)));
vi.push_back(unique_ptr(new int(2)));
sort(v.begin(), v.end(), indirect_less());  //result: {0, 2, 3}

Conclusion

In conclusion, unique_ptr is the safe equivalent of auto_ptr. Its small memory footprint and runtime efficiency make it a useful replacement for raw pointers. If you don’t need to share resources, unique_ptr is the right choice for you.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read