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

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.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist