Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Using unique_ptr, Part I

Last updated Jan 1, 2003.

If you’re looking for more up-to-date information on this topic, please visit our C/C++ Programming article, podcast, and store pages.

The turbulent history of auto_ptr eventually led to its deprecation in the C++0x Working Paper (I'm grateful to Howard Hinnant for pointing it out to me). C++0x introduces a new smart pointer class called unique_ptr which supports move semantics with at least the same efficiency of auto_ptr, but without the notorious limitations and runtime surprises that are associated with the latter. Find out more about the new unique_ptr class template and its usage here.

Why auto_ptr Had To Be Deprecated

The repeated modifications and patches of auto_ptr improved its safety to some extent but couldn't eliminate one serious flaw in its design: Calling a generic algorithm (e.g., std::sort()) that will operate on auto_ptr is risky because the generic algorithm might assume that something that looks like a copy operation -- is truly a copy operation. I'll show an example:

vector<auto_ptr<int> > vi;
//..populate vi
sort(vi.begin(), vi.end(), indirect_less());

Depending on the implementation of sort(), the above line of code might cause undefined behavior (often leading to a crash). The problem is that some implementations of sort() will pick an element out of the sequence, and store a local copy of it. Such implementations are perfectly valid and standard compliant. However, look what happens when they encounter vi:

value_type pivot = *mid_point; //seemingly a copy operation, but no quite so!

The sort() algorithm assumes that pivot and *mid_point 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. To fix this bug, auto_ptr was patched several years ago, disallowing standard containers to copy from const auto_ptr. This fix provides only a partial solution though. Users can still store auto_ptr objects in a built-in array. When you apply sort() to such an array, the results can be undefined behavior -- or perfectly valid, depending on the underlying implementation of sort().

Generally speaking, it's a bad idea to move from lvalues using copy syntax. Other syntax for moving should be used instead or else generic code is likely to initiate a move when a copy was intended, thus causing undefined behavior. Because auto_ptr moves from lvalues using copy syntax, it's fundamentally unsafe.

Deprecating auto_ptr

After years of debates, the C++ standards committee decided that auto_ptr should be deprecated. Since the deprecation alone will not make the problems of auto_ptr disappear it was decided that a new smart pointer class with more or less the same strict ownership semantics of auto_ptr -- but with a cleaner and safer interface -- should be added to C++0x. That replacement class is called unique_ptr. If you have used auto_ptr before, you will find unique_ptr's usage pretty straightforward. However, unlike auto_ptr, unique_ptr requires an explicit move operation for moving from lvalues; you can't use copy syntax for that purpose. Consequently, STL containers and algorithms, and in fact, any generic code will either fail to compile when operating on unique_ptr (instead of leaving the bug unreported and crashing at runtime), or it will produce valid results if unique_ptr is allowed. In addition to solving the safety problems of auto_ptr, the designers of unique_ptr decided to extend its functionality without adding space or runtime overhead (unless such overhead is explicitly requested) while still maintaining source compatibility with auto_ptr (except the ability to move from an lvalue with copy syntax).

Anything auto_ptr Can Do, But Better

It should be noted that unique_ptr is not a 100% source-compatible drop-in replacement for auto_ptr. If it were, the standards committee could simply patch auto_ptr instead of deprecating it and introducing a new class template as a replacement. That said everything you can do with auto_ptr, unique_ptr will do as well with the same syntax except for moving from lvalues:

#include <utility> 
using std::auto_ptr;
using std::unique_ptr;
// default construction
auto_ptr<int> ap;
unique_ptr<int> up;
// initialize with pointer
auto_ptr<int> ap(new int(9));
unique_ptr<int> up(new int(9));
// dereference
*ap = 23;
*up = 23;
// reset
ap.reset();
up.reset();
assert (sizeof(auto_ptr<T>) == sizeof(unique_ptr<T>));

When it comes to copy operations, there's a difference:

auto_ptr<int> ap1(new int);
auto_ptr<int> ap2 = ap1; // OK, albeit unsafe 
unique_ptr<int> up1(new int);
unique_ptr<int> up2 = up1; // compilation error: private copy ctor inaccessible

If you really want to transfer ownership from the lvalue unique_ptr, you have to use an explicit std::move() call:

unique_ptr<int> up1(new int);
unique_ptr<int> up2 = move(up1); // ok, explicit move

unique_ptr permits move from rvalues with copy syntax using a move constructor (which binds to rvalues), while blocking the copy from lvalues by making the copy constructor (which binds to lvalues) private:

template <class T>
class unique_ptr
{
public:
 unique_ptr(unique_ptr&& u);   // rvalues bind here
private:
 unique_ptr(const unique_ptr&); // lvalues bind here
};

In the next part of this series I will discuss passing unique_ptr to a function and returning unique_ptr from a function, using unique_ptr with algorithms and containers, customizing a deleter, and using unique_ptr for storing arrays.