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

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_ptrin 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_ptrsafely with STL containers and algorithms.

Replace auto_ptr with the new unique_ptrclass template.

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

vector > vi;//..populate visort(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_ptris the culprit.

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

Just Like auto_ptr, But Better
unique_ptrticks 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_ptrhas an almost identical interface:

#include  using namespace std;unique_ptr up1; //default constructionunique_ptr up2(new int(9)); //initialize with pointer*up2 = 23; //dereferenceup2.reset(); //resetassert (sizeof(auto_ptr)==sizeof(unique_ptr));assert (sizeof(auto_ptr)==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 ap1(new int);auto_ptr ap2=ap1; //OK but unsafe: move                       //operation in disguise unique_ptr up1(new int);unique_ptr up2=up1; //compilation error: private                         //copy ctor inaccessible

Instead, you must call move() when moving operation from an lvalue:

unique_ptr up2 = std::move(up1);//OK

This makes unique_ptrsafe 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_typeyou 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 > vi;v.push_back(unique_ptr(new int(8)));  v.push_back(unique_ptr(new int(4)));v.push_back(unique_ptr(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_ptrby value from a source:

unique_ptr source(int i){ return unique_ptr(new int(i));}void sink(unique_ptr);sink(source(5));//OK, implicit move from rvalue

Again, when moving from an lvalue you must use move()explicitly:

unique_ptr up=source(5);sink(up); //compilation error, implicit move from lvaluesink(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  void operator()(T*) {}; // do nothing};int main(){ int n=0; // stack memory. Safe, deleter does nothingunique_ptr 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 double* d =(double*) std::malloc(sizeof(double));//deleter calls free(), not delete unique_ptr pd(d, std::free); 

The interface of smart pointers to single objects differs fundamentally from that of smart pointers to arrays:

  • A single object smart pointer often supports derived-to-base conversions while array smart pointers must not support such conversions.
  • A dereference operator for a single object makes sense but for an array it makes less sense because it will dereference only the first element.
  • Operator [] makes sense only for an array smart pointer.

The designers of unique_ptr solved these discrepancies by creating a partial specializationfor arrays:

unique_ptr 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_ptrdoesn’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.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

More From DevX