DevX HomePage

Who's the Smartest of 'Em All? Get to Know std::unique_ptr

C++0x now offers a safer and extremely versatile smart pointer class called std::unique_ptr. Learn how to implement strict ownership semantics with unique_ptr and benefit from its diverse services—customized deleters, safe usage with containers and algorithms, and array handling.
td::auto_ptr was a move semantics pioneer. However, at that time, C++ didn't have the facilities for supporting move operations that were both safe and efficient. After years of distilling, C++0x brings you a superior alternative called unique_ptr. unique_ptr offers the same runtime and size efficiency of auto_ptr in addition to code safety and many other goodies, including array handling, and STL compatibility.


Your program needs a smart pointer that implements the strict ownership semantics but you cannot use auto_ptr safely with STL containers and algorithms.


Replace auto_ptr with the new unique_ptr class template.

Presenting the Problem
Suppose your program calls std::sort() to sort a sequence of auto_ptr objects:


vector<auto_ptr<int> > vi;
//..populate vi
sort(vi.begin(), vi.end(), indirect_less());
Depending on the underlying implementation of sort(), the above call could work perfectly—or it might cause a runtime crash. The problem is that some implementations of sort() pick an element out of the sequence, storing a local copy thereof:

value_type pivot_elem = *midpoint;//not a copy operation!
sort() assumes that pivot_elem and *midpoint are equivalent. However when value_type is an auto_ptr, this assumption fails because what appears to be a copy operation is in fact a move operation. Consequently, the algorithm fails. There's nothing amiss with the implementation of sort(). Rather, auto_ptr is the culprit.

Because of such bugs, using auto_ptr with Standard Library containers and algorithms is forbidden. What other alternative do you have?




Just Like auto_ptr, But Better
unique_ptr ticks the right boxes. It implements a strict ownership semantics, it's slim and it's fast, with a memory footprint identical to that of a native pointer.

If you've used auto_ptr before you will notice that unique_ptr has an almost identical interface:


#include <utility>
using namespace std;
unique_ptr<int> up1; //default construction
unique_ptr<int> up2(new int(9)); //initialize with pointer
*up2 = 23; //dereference
up2.reset(); //reset
assert (sizeof(auto_ptr<T>)==sizeof(unique_ptr<T>));
assert (sizeof(auto_ptr<T>)==sizeof(unique_ptr&lT>));
The main difference between auto_ptr and unique_ptr becomes visible in move operations. While auto_ptr sometimes disguises move operations as copy-operations, unique_ptr will not let you use copy semantics when you're actually moving an lvalue unique_ptr:

auto_ptr<int> ap1(new int);
auto_ptr<int> ap2=ap1; //OK but unsafe: move
//operation in disguise
unique_ptr<int> up1(new int);
unique_ptr<int> up2=up1; //compilation error: private
//copy ctor inaccessible
Instead, you must call move() when moving operation from an lvalue:

unique_ptr<int> up2 = std::move(up1);//OK
This makes unique_ptr safe for usage with containers and algorithms.

Containers and Algorithms
If a container of unique_ptr objects moves elements internally instead of copying them, your code will work without problems. If, however, a container or an algorithm uses copy semantics for its value_type you will get a compilation error. Thus, you won't encounter a run time error due to a move operation being mistaken for a copy:


vector<unique_ptr<int> > vi;
v.push_back(unique_ptr<int>(new int(8)));
v.push_back(unique_ptr<int>(new int(4)));
v.push_back(unique_ptr<int>(new int(1)));
sort(v.begin(),v.end(),indirect_less()); //result {1,4,8}




The Sink and Source Idiom
A source is a function that acquires a resource and passes the ownership of that resource to another function called a sink. A sink uses the resource and when it's done, it disposes of that resource. You can return unique_ptr by value from a source:


unique_ptr<int> source(int i)
{
return unique_ptr<int>(new int(i));
}
void sink(unique_ptr<int>);
sink(source(5));//OK, implicit move from rvalue
Again, when moving from an lvalue you must use move() explicitly:

unique_ptr<int> up=source(5);
sink(up); //compilation error, implicit move from lvalue
sink(move(up)); //OK
To generalize, an implicit move from an rvalue unique_ptr is always safe and therefore may use copy syntax. Moving from an lvalue unique_ptr requires an explicit move() call.

Customizable Deleters
A deleter is a function that the smart pointer uses for deallocating its resource. By default, unique_ptr's deleter uses plain delete, as expected. However, certain resources require special cleanup operations. Here's how you install a custom deleter that has no effect:


struct nop_deleter
{
template <class T> void operator()(T*) {}; // do nothing
};
int main()
{
int n=0;
// stack memory. Safe, deleter does nothing
unique_ptr<int, nop_deleter> pi(&n);
*pi = 10;
assert(sizeof(pi)==sizeof(int*)); //zero space overhead
}
In the following example, a custom deleter calls std::free() to deallocate an object allocated using std::malloc():

#include <cstdlib>
double* d =(double*) std::malloc(sizeof(double));
//deleter calls free(), not delete
unique_ptr<int, void (*)(void*)> pd(d, std::free);
Arrays
The interface of smart pointers to single objects differs fundamentally from that of smart pointers to arrays: The designers of unique_ptr solved these discrepancies by creating a partial specialization for arrays:

unique_ptr<int[]> arrup (new int[3]);
arrup[0]=1;
arrup[1]=2;
arrup[2]=3;
The trailing [] after the type in the unique_ptr declaration indicates that the "array version" (no dereference, no conversions, indexing supported) shall be used. As expected, the default deleter here is delete[].

Elegant Smart
unique_ptr isn't just a safer alternative to the deprecated auto_ptr. It offers services that auto_ptr doesn't support, including safe usage with containers and algorithms, customizable deleters, and array handling. Surprisingly, this versatility doesn't exact a performance toll; you only pay for what you're actually using.

Danny Kalev is a certified system analyst and software engineer specializing in C++ and theoretical linguistics. He has an MA degree in general linguistics and is the author of Informit C++ Reference Guide and The ANSI/ISO Professional C++ Programmer's Handbook. Danny was a member of the C++ standards committee between 1997 and 2000. Danny recently finished his MA in general linguistics summa cum laude. In his spare time he likes to listen to classical music, read Victorian literature, and explore new natural and formal languages alike. He also gives lectures about programming and applied linguistics at academic institutes.


DevX is a division of Internet.com.
© Copyright 2010 Internet.com. All Rights Reserved. Legal Notices