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.