Document number: N1856=05-0116

Howard E. Hinnant
2005-08-26

Rvalue Reference Recommendations for Chapter 20

Contents

Related papers

Rvalue Reference Recommendations for Chapter 21
Rvalue Reference Recommendations for Chapter 23
Rvalue Reference Recommendations for Chapter 24
Rvalue Reference Recommendations for Chapter 25
Rvalue Reference Recommendations for Chapter 26
Rvalue Reference Recommendations for Chapter 27

Introduction

This paper recommends proposed wording with respect to the rvalue reference for the C++0X working draft. This paper restricts its scope to Chapter 20 "General utilities library" for the purpose of breaking the library work associated with the rvalue reference up into manageable chunks. This paper largely follows the lead of N1771: Impact of the rvalue reference on the Standard Library, but adds more detail as appropriate.

With the exception of this introduction, all non-proposed wording will have a background color and formatting that

looks like this, so that motivation and description is more easily distinguished from proposed wording.

In the proposed wording below, text to be inserted is formatted like this, while wording to be deleted is formatted like this.

The proposed wording in this paper:

20.1 - Requirements

The following proposed wordings accomplish several things:

The Swappable concept is not mentioned herein only because it is already part of the working draft. The rvalue reference work depends upon, and assumes that the Swappable concept will remain in the working draft through standardization.

The motivation for separating out the different concepts from the old definition of CopyConstructible is to allow containers and algorithms to work with movable but non-copyable types. A clean separation of concepts is helpful in this regard.

The Addressable concept is not strictly needed for C++0X, even though we currently require it for a container's value_type. Containers needing the actual address of a value_type can use something along the lines of boost::addressof instead of the value_type's operator&(). This paper offers no recommendation on whether to include or exclude an Addressable concept in C++0X.

20.1.3 - Move construction

-1- In the following Table 30, T is a type to be supplied by a C++ program instantiating a template, t is an identifier, and rv is a non-const rvalue of type T.

Insert the following table:

Table 30: MoveConstructible requirements
expression post-condition
T t = rv t is equivalent to the value of rv before the construction

[Note: There is no requirement on the value of rv after the construction. -- end note]

20.1.3 4 - Copy construction

-1- In the following Table 30 31, T is a type to be supplied by a C++ program instantiating a template, t is an identifier a value of type T, and u is a value of type (possibly const) const T.

Delete the following table:

Table 30: CopyConstructible requirements
expression return type requirement
T(t)   t is equivalent to T(t)
T(u)   u is equivalent to T(u)
t.~T()    
&t() T* denotes the address of t
&u() const T* denotes the address of u

Insert the following table:

Table 31: CopyConstructible requirements
expression post-condition
T t = u The value of u is unchanged and is equivalent to t

[Note: A type that satisfies the CopyConstructible requirements also satisfies the MoveConstructible requirements. -- end note]

20.1.5 - Move assignment

-1- In the following Table 32, T is a type to be supplied by a C++ program instantiating a template, t is a value of type T, and rv is a non-const rvalue of type T.

Insert the following table:

Table 32: MoveAssignable requirements
expression return type return value post-condition
t = rv T& t t is equivalent to the value of rv before the assignment

[Note: There is no requirement on the value of rv after the assignment. -- end note]

20.1.6 - Copy assignment

-1- In the following Table 33, T is a type to be supplied by a C++ program instantiating a template, t is a value of type T, and rv is a non-const rvalue of type T.

Insert the following table:

Table 33: CopyAssignable requirements
expression return type return value post-condition
t = u T& t t is equivalent to u, the value of u is unchanged

[Note: A type that satisfies the CopyAssignable requirements also satisfies the MoveAssignable requirements. -- end note]

Delete the existing definition of Assignable in 23.1, paragraph 4.

20.1.9 - Destruction requirements

-1- In the following Table 35, T is a type to be supplied by a C++ program instantiating a template, and u is a value of type (possibly const) T.

Insert the following table:

Table 35: Destructible requirements
expression post-condition
u.~T() All resources owned by u are reclaimed, no exception is propagated

20.1.6 10 - Allocator requirements

Append the following two rows to the Table in paragraph 2: Descriptive variable definitions:

V A type convertible to T
v A value of type V

Append the following row to the Table in paragraph 2: Allocator requirements:

a.construct(p,v) (not used) Effect: ::new((void*)p) T(std::forward<V>(v))

The new construct overload is typically implemented as a member template function. It obsoletes the current non-member template construct member, but the current construct member is retained for binary backwards compatibility. The new overload allows containers to move construct rvalues from single element insert functions. It also allows convertible types V to be used to construct types T. This is necessary (for example) in map which must move construct a pair<const key, value> from an rvalue pair<key, value>, because it is not possible to move from a pair<const key, value>.

Allocators that are not move-aware, and don't meet this requirement will continue to work in move-aware containers of types meeting the CopyConstructible requirement. But they will fail at compile time for move-aware containers of types only meeting the MoveConstructible requirement. That is, if you want to put moveable but non-copyable types into a move-aware container, the container's allocator must have this construct overload. Therefore, this change is 100% backwards compatible, but required for the proposed new functionality in C++0X.

20.2 - Utility components

In section 20.2, the proposed wording accomplishes two tasks:

  1. Introduce the move and forward helper functions.
  2. Make pair move-aware.

The move and forward helper functions provide a clean and descriptive syntax to clients for invoking move semantics for lvalues, and for implementing perfect forwarding in generic code.

The second item allows expensive-to-copy-but-cheaply-movable types to be cheaply moved into a pair. It also allows pair to contain non-copyable but movable types. In either case the resulting pair itself also becomes cheaply movable (assuming its contained items are cheaply movable). Both namespace scope and member swap are added to complete pair's efficient, generic interface.

-1- This subclause contains some basic function and class templates that are used throughout the rest of the library.

Header <utility> synopsis

namespace std {
  // 20.2.1, operators:
  namespace rel_ops {
    template <class T> bool operator !=(const T&, const T&);
    template <class T> bool operator > (const T&, const T&);
    template <class T> bool operator <=(const T&, const T&);
    template <class T> bool operator >=(const T&, const T&);
  }


  // 20.2.2, forward / move:
  template <class T> struct identity {typedef T type;};
  template <class T> T&& forward(typename identity<T>::type&&);
  template <class T> typename remove_reference<T>::type&& move(T&&);

  // 20.2.2 3, pairs:
  template <class T1, class T2> struct pair;
  template <class T1, class T2>
    bool operator ==(const pair<T1,T2>&, const pair<T1,T2>&);
  template <class T1, class T2>
    bool operator < (const pair<T1,T2>&, const pair<T1,T2>&);
  template <class T1, class T2>
    bool operator !=(const pair<T1,T2>&, const pair<T1,T2>&);
  template <class T1, class T2>
    bool operator > (const pair<T1,T2>&, const pair<T1,T2>&);
  template <class T1, class T2>
    bool operator >=(const pair<T1,T2>&, const pair<T1,T2>&);
  template <class T1, class T2>
    bool operator <=(const pair<T1,T2>&, const pair<T1,T2>&);
  template <class T1, class T2>
    void swap(pair<T1,T2>&, pair<T1,T2>&);
  template <class T1, class T2>
    void swap(pair<T1,T2>&&, pair<T1,T2>&);
  template <class T1, class T2>
    void swap(pair<T1,T2>&, pair<T1,T2>&&);
  template <class T1, class T2> pair<T1,T2> make_pair(T1, T2);
}

20.2.2 - move/forward helpers

-1- The library provides templated helper functions to simplify applying move semantics to an lvalue, and to simplify the implementation of forwarding functions.


template <class T> struct identity {typedef T type;};

-2- The use of identity in forward forces client code to explicitly specify the template parameter. This is necessary in order to get the correct forwarding semantics.


template <class T> T&& forward(typename identity<T>::type&& t);

-3- Returns: t.

-4- Note: If T is an lvalue-reference type, t will be returned from forward as an lvalue. Otherwise t it will be returned as an rvalue.

-5- Example:


template <class T, class A1, class A2>
shared_ptr<T>
factory(A1&& a1, A2&& a2)
{
    return shared_ptr<T>(new T(forward<A1>(a1),
                               forward<A2>(a2)));
}

struct A
{
    A(int&, const double&);
};

int main()
{
    shared_ptr<A> sp1 = factory<A>(2, 1.414);  // does not compile,
                                               // 2 will not bind to int&
    int i = 2;
    shared_ptr<A> sp2 = factory<A>(i, 1.414);  // ok
}

-- end example

-6- In the above example, A1 is deduced as int in the first call to factory, and thus 2 is forwarded to A's constructor as an int&& (an rvalue). However in the second call to factory A1 is deduced as int&, and therefore i is forwarded to A's constructor as an int& (an lvalue). In both calls to factory, A2 is deduced as double with the result that 1.414 is passed to A's constructor as a double&& (an rvalue).


template <class T> typename remove_reference<T>::type&& move(T&& t);

-7- Returns: t.

-8- Note: t is returned as an rvalue.

-9- Example:


template <class T, class A1>
shared_ptr<T>
factory(A1&& a1)
{
    return shared_ptr<T>(new T(forward<A1>(a1)));
}

struct A
{
    A();
    A(const A&);  // lvalues are copied from
    A(A&&);       // rvalues are moved from
};

int main()
{
    A a;
    shared_ptr<A> sp1 = factory<A>(a);        // "a" binds to A(const A&)
    shared_ptr<A> sp2 = factory<A>(move(a));  // "a" binds to A(A&&)
}

-- end example

-10- In the above example the first call to factory deduces A1 as A& and therefore a is forwarded as a non-const lvalue which binds to the A(const A&) constructor, copying from a. However in the second call of factory, due to the call of move(a), A1 in factory is deduced as simply A. Therefore forward forwards an rvalue A to the constructor which subsequently binds to A(A&&) resulting in a being moved from.

20.2.2 3 - Pairs

-1- The library provides a template for heterogeneous pairs of values. The library also provides a matching function template to simplify their construction.

template <class T1, class T2>
struct pair {
  typedef T1 first_type;
  typedef T2 second_type;

  T1 first;
  T2 second;
  pair();
  pair(const T1& x, const T2& y);
  template<class U, class V> pair(U&& x, V&& y);
  pair(pair&& p);
  template<class U, class V> pair(const pair<U, V>& p);
  template<class U, class V> pair(pair<U, V>&& p);

  pair& operator=(pair&& p);
  template<class U, class V> pair& operator=(pair<U, V>&& p);

  void swap(pair&& p);
};

...

pair(const T1& x, const T2& y);

-3- Effects: The constructor initializes first with x and second with y.


template<class U, class V> pair(U&& x, V&& y);

-4- Effects: The constructor initializes first with forward<U>(x) and second with forward<V>(y).


pair(pair&& p);

-5- Effects: The constructor initializes first with move(p.first) and second with move(p.second).


template<class U, class V> pair(pair<U, V>&& p);

-6- Effects: The constructor initializes first with move(p.first) and second with move(p.second).


pair& operator=(pair&& p);

-7- Effects: Assigns first with move(p.first) and second with move(p.second).

-8- Returns: *this.


template<class U, class V> pair& operator=(pair<U, V>&& p);

-9- Effects: Assigns first with move(p.first) and second with move(p.second).

-10- Returns: *this.

template<class U, class V> pair(const pair<U, V>& p);

-4 11- Effects: Initializes members from the corresponding members of the argument, performing implicit conversions as needed.


void swap(pair&& p);

-12- Effects: Swaps first with p.first and second with p.second.

-13- Requires: first_type and second_type must be Swappable.

...

-6 15Returns: x.first < y.first || (!(y.first < x.first) && x.second < y.second).


template <class T1, class T2>
  void swap(pair<T1,T2>& x, pair<T1,T2>& y);
template <class T1, class T2>
  void swap(pair<T1,T2>&& x, pair<T1,T2>& y);
template <class T1, class T2>
  void swap(pair<T1,T2>& x, pair<T1,T2>&& y);

-16- Effects: x.swap(y)

20.4 - Memory

There are three changes for section 20.4:

  1. Add a construct member function to the default allocator.
  2. Deprecate auto_ptr.
  3. Add unique_ptr to replace the functionality provided by auto_ptr.

In the <memory> synopsis delete:

template<class X> class auto_ptr;

and add at the same place:

template<class X> class unique_ptr;

20.4.1 - The default allocator

The changes below simply implement for the default allocator the updated allocator requirements as previously discussed in section 20.1.10.

template <class T>
class allocator {
 public:
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
  typedef T* pointer;
  typedef const T* const_pointer;
  typedef T& reference;
  typedef const T& const_reference;
  typedef T value_type;
  template <class U> struct rebind { typedef allocator<U> other; };

  allocator() throw();
  allocator(const allocator&) throw();
  template <class U> allocator(const allocator<U>&) throw();
  ~allocator() throw();

  pointer address(reference x) const;
  const_pointer address(const_reference x) const;

  pointer allocate(
    size_type, allocator<void>::const_pointer hint = 0);
  void deallocate(pointer p, size_type n);
  size_type max_size() const throw();

  void construct(pointer p, const T& val);
  template <class U> void construct(pointer p, U&& val);
  void destroy(pointer p);
};

...


template <class U> void construct(pointer p, U&& val);

-13- Effects: ::new((void*)p) T(std::forward<U>(val))

20.4.5 - Class template auto_ptr

Why deprecate auto_ptr?

The auto_ptr class template was a pioneer in the area of move semantics. auto_ptr has always had move semantics - the ability to transfer its resource from one object to another. An early auto_ptr design accomplished this transfer using copy syntax and with a const auto_ptr as the source:

const auto_ptr<int> source(new int);
auto_ptr<int> target = source;  // move from const source to target

With such a design, one could put auto_ptr into a container:

vector<auto_ptr<int> > vec;

However field experience with this design revealed subtle problems. Namely:

sort(vec.begin(), vec.end(), indirect_less());

Depending upon the implementation of sort, the above line of reasonable looking code may or may not execute as expected, and may even crash! The problem is that some implementations of sort will pick an element out of the sequence, and store a local copy of it.

...
value_type pivot_element = *mid_point;
...

The algorithm assumed that after this construction that pivot_element and *mid_point were equivalent. However when value_type turned out to be an auto_ptr, this assumption failed, and subsequently so did the algorithm.

The fix to this problem was to make auto_ptr inhospitable to containers by disallowing "copying" from a const auto_ptr. With such an auto_ptr, one gets a compile time error if you try to put it in a container.

However that fix really only saves clients from sorting standard containers of auto_ptr. It does not prevent clients from sorting client-defined containers of auto_ptr, or even built-in arrays auto_ptr. For example the following code will compile today:

#include <algorithm>
#include <memory>

struct indirect_less
{
    template <class T>
    bool operator()(const T& x, const T& y)
    {
        return *x < *y;
    }
};

int main()
{
    std::auto_ptr<int> ptrs[3];
    ptrs[0].reset(new int(3));
    ptrs[1].reset(new int(2));
    ptrs[2].reset(new int(1));
    std::sort(ptrs, ptrs+3, indirect_less()); // run time error?!
}

Whether or not it runs correctly is entirely dependent upon the implementation of std::sort. And this does not represent a problem with std::sort. Calling any generic code, whether std or not, that will operate on auto_ptr is risky because the generic code may assume that something that looks like a copy operation, actually is a copy operation.

Conclusion:

One should not move from lvalues using copy syntax. Other syntax for moving should be used instead. Otherwise generic code is likely to initiate a move when a copy was intended.

auto_ptr moves from lvalues using copy syntax and is thus fundamentally unsafe.

Copy section 20.4.5, in its entirety, to a new section, D.8, and title that section: auto_ptr. Then modify section 20.4.5 as shown below to introduce unique_ptr.

Addition - Class template unqiue_ptr

Covering auto_ptr functionality

Despite the fact that auto_ptr is fundamentally unsafe, we should not just deprecate it without simultaneously providing the same functionality some other way. auto_ptr may be unsafe, but it is also very useful, especially for RAII handling of heap pointers.

And this is the goal of unique_ptr proposed below. unique_ptr is intended as a safer auto_ptr replacement. However because unique_ptr does not move from lvalues with copy syntax, it is not a 100% source-compatible drop in replacement for auto_ptr. If it were, we could just fix auto_ptr instead of deprecating it and introducing a new class template.

It turns out that we can do much more than merely make unique_ptr a safer auto_ptr. The proposed unique_ptr offers significant additional functionality over auto_ptr without adding space or time overhead (unless such overhead is explicitly requested), and while maintaining source compatibility with auto_ptr sans the ability to move from an lvalue with copy syntax (but you can move from an lvalue with move syntax).

Indeed, everything you can do with auto_ptr, unique_ptr will do too, with the same syntax ... except for moving from lvalues:

// default construct
auto_ptr<int>   ap;
unique_ptr<int> up;

// construct with pointer
auto_ptr<int>   ap(new int(1));
unique_ptr<int> up(new int(1));

// dereference
*ap = 2;
*up = 2;

// reset
ap.reset();
up.reset();
// etc.

And the sizeof(auto_ptr<T>) == sizeof(unique_ptr<T>).

However copy semantics is disabled with unique_ptr:

auto_ptr<int> ap1(new int);
auto_ptr<int> ap2 = ap1;  // ok, but unsafe implicit move

unique_ptr<int> up1(new int);
unique_ptr<int> up2 = up1;  // compile time error: illegal access to
                            // private copy constructor

If you really want to transfer ownership from the lvalue unique_ptr, you move from it just as you would any other type:

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

The move helper function simply casts an lvalue to an rvalue (with zero expense).

template <class T>
inline
typename std::tr1::remove_reference<T>::type&&
move(T&& p)
{
    return p;
}

And unique_ptr is set up to move from rvalues with copy syntax using the 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
};

Therefore clients can move from rvalues just as they do with auto_ptr (with copy syntax), and from lvalues using the move helper function. Both expressions construct the target using the move constructor (unique_ptr(unique_ptr&&)).

Corollary:

Even though it is unsafe to move from an lvalue with copy syntax, it is both safe and desirable to move from an rvalue with copy syntax. When an rvalue binds to a function, that function has a unique reference to the rvalue and thus can safely modify it.

Moving from rvalues with copy syntax comes in handy when returning a unique_ptr from a function:

unique_ptr<int>
factory(int i)
{
    return unique_ptr<int>(new int(i));
}
...
unique_ptr<int> up = factory(1);  // ok, safe to implicitly move from rvalues

When passing unique_ptr to a function which takes a unique_ptr by value, then just like auto_ptr the pointer ownership will be transferred to that function. However unlike auto_ptr that transfer will not happen implicitly from an lvalue. But it will happen implicitly from an rvalue. This is safe because no one has a reference to the rvalue to notice the change in its value. For example:

auto_ptr<int>
ap_factory(int i)
{
    return auto_ptr<int>(new int(i));
}

unique_ptr<int>
up_factory(int i)
{
    return unique_ptr<int>(new int(i));
}

void ap_sink(auto_ptr<int>);
void up_sink(unique_ptr<int>);

...

auto_ptr<int> ap = ap_factory(1);
ap_sink(ap);                  // ok, but unsafe implicit move
unique_ptr<int> up = up_factory(1);
up_sink(up);                  // compile time error - implicit move from lvalue
up_sink(move(up));            // explicit move ok
up_sink(up_factory(1));       // ok, implicit move from rvalue safe
Regaining auto_ptr functionality

auto_ptr was originally intended to allow for easy conversions between base and derived types. However recent changes to the standard result in auto_ptr<Base> not cooperating as originally intended with auto_ptr<Dervied>:

struct B {virtual ~B() {}};
struct D : B {};

auto_ptr<D>
ap_factory()
{
    return auto_ptr<D>();
}

void ap_sink(auto_ptr<B>);

void test()
{
    auto_ptr<B> ap = ap_factory();  // error:
                                    // no suitable copy constructor
    ap_sink(ap_factory());   // error: no suitable copy constructor
}

This functionality is regained with unique_ptr. There are no longer complicated auto_ptr_ref conversions to navigate. unique_ptr simply uses straight-forward constructors to bind to rvalues of related unique_ptr types.

struct B {virtual ~B() {}};
struct D : B {};

unique_ptr<D>
up_factory()
{
    return unique_ptr<D>();
}

void up_sink(unique_ptr<B>);

void test()
{
    unique_ptr<B> up = up_factory();  // ok
    up_sink(up_factory());            // ok
}
Beyond auto_ptr

It turns out that unique_ptr can offer functionality far beyond what auto_ptr can.

Use with containers and algorithms

You can safely put unique_ptr into containers. If the containers move elements around internally instead of copy them around (as proposed for the standard containers), unique_ptr will work without problems. If the container does use copy semantics for its value_type, then the client will be notified immediately (at compile time). There is no chance of a run time error due to a move being mistaken for a copy.

Similarly you can safely use unique_ptr with the standard algorithms, or with any generic code. Either the generic code will use move semantics, and unique_ptr will just work, or it will use copy semantics, and the client will be notified at compile time of the error.

vector<unique_ptr<int> > v;
v.push_back(unique_ptr<int>(new int(3)));  // load vector with unique_ptr's
v.push_back(unique_ptr<int>(new int(2)));
v.push_back(unique_ptr<int>(new int(1)));
sort(v.begin(), v.end(), indirect_less());  // sort, obtaining {1, 2, 3}
Custom Deleter

Additionally unique_ptr can be given a statically bound custom deleter. This is in contrast to shared_ptr's dynamically bound custom deleter. The key characteristic of a statically bound custom deleter is that space for it can be optimized away if the deleter is an "empty" class (assuming support for the empty base optimization). And of course the default deleter is an empty class which simply calls delete.

struct my_deleter
{
    template <class T>
    void operator()(T*) {}; // do nothing
};

int main()
{
    int i = 0;
    typedef unique_ptr<int, my_deleter> Ptr;
    Ptr p(&i);  // can safely point to stack because deleter does nothing
    *p = 1;
    assert(i == 1);                       // *p refers to i
    assert(sizeof(Ptr) == sizeof(int*));  // zero overhead!
}

The space optimization for unique_ptr's deleter can easily be accomplished with a tool such as boost::compressed_pair.

template<class T, class D = default_delete<T> >
class unique_ptr
{
...
private:
    compressed_pair<T*, D> ptr_;  // exposition only
};

Note that simply deriving unique_ptr from its deallocator type is not a valid implementation technique as the deallocator is allowed to be an ordinary function pointer (which can not be derived from). Indeed use of a function pointer as a deallocator can be a quite powerful technique, allowing for deallocators to point across shared library boundaries.

typedef unique_ptr<int, void (*)(void*)> Ptr;
int* i = (int*)malloc(sizeof(int));
Ptr p(i, free); // deallocate pointer with std::free

Of course when a function pointer is used as the custom deleter for unique_ptr, then the sizeof(unique_ptr) will swell accordingly. However this is a feature that you only pay for if you use.

Reference Deleter

One can also specify an lvalue-reference to a deleter as the deleter type. This is especially handy in generic code when you need to temporarily, but aggressively grab ownership of a pointer using a deleter you know nothing about. For example:

template<class T, class D>
class A
{
    ...
    void foo(T* p);
private:
    T* p_;
    D  deleter_;
    ...
};

// A::foo establishes ownership of p, but
// must acquire other resources to do so.  A local
// unique_ptr is used as an aid to hold on to p while
// those other resources are acquired.
template<class T, class D>
void
A<T, D>::foo(T* p)
{
    // Establish preliminary ownership without requiring
    // a copy of the deleter D
    std::unique_ptr<T, D&> hold(p, deleter_);  // no throw
    // acquire resources                       // if throws,
    // ...                                     //    deleter_(p) executed
    // transfer ownership to A
    p_ = hold.release();                       // no throw
}

In this example a local unique_ptr establishes a hold on the pointer. A reference to the deleter is passed to the unique_ptr so that there is no chance that copying the deleter can throw, and so that if the latter resource acquisition step does throw, any relevant state in A's deleter will be updated as that deleter deallocates p.

unique_ptr For Arrays

Often when people see a smart pointer with a customizable deleter, they think this is how they might handle pointers to arrays (which require delete [] as opposed to delete). However a smart pointer to an array should have a fundamentally different interface than a smart pointer to a single object.

Therefore a unique_ptr to an array of objects should sport a slightly different interface in addition to the delete [] operation. This is handled by a partial specialization resulting in a very intuitive interface:

unique_ptr<int[]> ua(new int[3]);
ua[0] = 5;
ua[1] = 4;
ua[2] = 3;

By including the trailing "[]" after the type in the unique_ptr declaration, one states that one desires the "array interface" for the smart pointer (no dereference, no conversions, has indexing). Additionally the default deleter for this interface is delete []. However it can be overridden in the same manner as shown for the non-array variant of unique_ptr.

struct my_deleter
{
    template <class T>
    void operator()(T*) {}; // do nothing

    template <class T>
    void operator()(T*, size_t) {}; // do nothing
};

int main()
{
    int ia[3] = {0};
    typedef unique_ptr<int[], my_deleter> Ptr;
    Ptr p(ia);
    p[2] = 5;
    assert(ia[2] == 5);
    assert(sizeof(Ptr) == sizeof(int*));
}

If the size of the array is known at compile time, that information can be included in the type of the unique_ptr in the obvious way:

unique_ptr<int[3]> ua(new int[3]);
ua[0] = 5;
ua[1] = 4;
ua[2] = 3;

This information may be used by the implementation for range checking the indexing operator (though this is not required). The size will also be passed to the deleter as the second parameter when the pointer is deallocated. The default deleter ignores this information though.

Knowing when to stop

The functionality shown so far adds no run time overhead. Space overhead is only added if the custom deleter is not an empty class. Otherwise unique_ptr has precisely the same overhead as auto_ptr which has precisely the same overhead as a simple built-in pointer.

Adding support for a dynamically bound deleter (like shared_ptr) would incur space and time overhead which is not appropriate for unique_ptr, and the functionality gained over just simply specifying an ordinary function pointer as a deleter is minimal.

Adding support to store the size of the array in the run time length array variant of unique_ptr would add a word of overhead that is often not needed. When it is needed, clients can easily build a higher level smart pointer on top of unique_ptr. The size could even be stored in a custom deleter. The point is that storing the size of the array requires overhead, and it is easier for clients to add overhead than subtract it.

Thus unique_ptr is a minimalist smart pointer design - a solid foundation upon which further work can be built.

20.4.5 Class template auto_ptr unique_ptr

Synopsis

namespace std {

template <class T> struct default_delete;
template <class T> struct default_delete<T[]>;
template <class T, size_t N> struct default_delete<T[N]>;

template<class T, class D = default_delete<T> > class unique_ptr;
template<class T, class D> class unique_ptr<T[], D>;
template<class T, class D, size_t N> class unique_ptr<T[N], D>;

template<class T, class D> void swap(unique_ptr<T,D>&  x, unique_ptr<T,D>&  y);
template<class T, class D> void swap(unique_ptr<T,D>&& x, unique_ptr<T,D>&  y);
template<class T, class D> void swap(unique_ptr<T,D>&  x, unique_ptr<T,D>&& y);

template<class T1, class D1, class T2, class D2>
  bool operator==(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);
template<class T1, class D1, class T2, class D2>
  bool operator!=(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);
template<class T1, class D1, class T2, class D2>
  bool operator <(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);
template<class T1, class D1, class T2, class D2>
  bool operator<=(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);
template<class T1, class D1, class T2, class D2>
  bool operator >(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);
template<class T1, class D1, class T2, class D2>
  bool operator>=(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);

}  // namespace std

-1- Template unique_ptr stores a pointer to an object and deletes that object using the associated deleter when it itself is destroyed (such as when leaving block scope 6.7).

-2- The unique_ptr provides a semantics of strict ownership. A unique_ptr owns the object it holds a pointer to. A unique_ptr is not CopyConstructible, nor CopyAssignable, however it is MoveConstructible and MoveAssignable. [Note: The uses of unique_ptr include providing exception-safety for dynamically allocated memory, passing ownership of dynamically allocated memory to a function, and returning dynamically allocated memory from a function. --end note]

20.4.5.1 Default deleters

template <class T>
struct default_delete
{
    default_delete();
    template <class U>
        default_delete(const default_delete<U>&);
    void operator() (T* ptr) const;
};
default_delete();

-1- Effects: Default constructs a default_delete.

template <class U>
    default_delete(const default_delete<U>&);

-2- Effects: Constructs a default_delete<T> from a default_delete<U>.

void operator() (T* ptr) const;

-3- operator() calls delete on ptr. A diagnostic is required if T is an incomplete type.

template <class T>
struct default_delete<T[]>
{
    void operator() (T* ptr) const;
};
void operator() (T* ptr) const;

-4- operator() calls delete [] on ptr. A diagnostic is required if T is an incomplete type.

template <class T, size_t N>
struct default_delete<T[N]>
{
    void operator() (T* ptr, size_t) const;
};
void operator() (T* ptr, size_t) const;

-5- operator() calls delete [] on ptr. A diagnostic is required if T is an incomplete type. The size_t parameter is ignored.

20.4.5.2 unique_ptr for non-array objects

template<class T, class D = default_delete<T> >
class unique_ptr
{
public:
    typedef T element_type;
    typedef D deleter_type;

    // constructors
    unique_ptr();
    explicit unique_ptr(T* p);
    unique_ptr(T* p, implementation defined (see description below) d);
    unique_ptr(T* p, implementation defined (see description below) d);
    unique_ptr(unique_ptr&& u);
    template <class U, class E>
        unique_ptr(unique_ptr<U, E>&& u);

    // destructor
    ~unique_ptr();

    // assignment
    unique_ptr& operator=(unique_ptr&& u);
    template <class U, class E>
        unique_ptr& operator=(unique_ptr<U, E>&& u);
    unique_ptr& operator=(unspecified-pointer-type);

    // observers
    T& operator*() const;
    T* operator->() const;
    T* get() const;
    deleter_type&       get_deleter();
    const deleter_type& get_deleter() const;
    operator unspecified-bool-type() const;

    // modifiers
    T* release();
    void reset(T* p = 0);
    void swap(unique_ptr&& u);

private:
    // disable copy from lvalue
    unique_ptr(const unique_ptr&);
    template <class U, class E> unique_ptr(const unique_ptr<U, E>&);
    unique_ptr& operator=(const unique_ptr&);
    template <class U, class E> unique_ptr& operator=(const unique_ptr<U, E>&);
};

-1- The default type for the template parameter D is default_delete. A client-supplied template argument D must be a function pointer or functor for which given a value d of type D, and a pointer ptr of type T*, the expression d(ptr) is valid and has the effect of deallocating the pointer as appropriate for that deleter. D may also be an lvalue-reference to a deleter.

-2- It is intended that if the deleter D maintains state, that this state stay with the associated pointer as ownership is transferred from unique_ptr to unique_ptr. The deleter state need never be copied, only moved or swapped as pointer ownership is moved around. That is, the deleter need only be MoveConstructible, MoveAssignable and Swappable, and need not be CopyConstructible (unless copied into the unique_ptr) nor CopyAssignable.

20.4.5.2.1 unique_ptr constructors

unique_ptr();

-1- Requires: D must be default constructible, and that construction must not throw an exception. D must not be a reference type.

-2- Effects: Constructs a unique_ptr which owns nothing.

-3- Postconditions: get() == 0. get_deleter() returns a reference to a default constructed deleter D.

-4- Throws: nothing.

unique_ptr(T* p);

-5- Requires: The expression D()(p) must be well formed. The default constructor of D must not throw an exception. D must not be a reference type.

-6- Effects: Constructs a unique_ptr which owns p.

-7- Postconditions: get() == p. get_deleter() returns a reference to a default constructed deleter D.

-8- Throws: nothing.

unique_ptr(T* p, implementation defined d);
unique_ptr(T* p, implementation defined d);

-9- The signature of these constructors depends upon whether D is a reference type or not. If D is non-reference type A, then the signatures are:

unique_ptr(T* p, const A& d);
unique_ptr(T* p, A&& d);

-10- If D is an lvalue-reference type A&, then the signatures are:

unique_ptr(T* p, A& d);
unique_ptr(T* p, A&& d);

-11- If D is an lvalue-reference type const A&, then the signatures are:

unique_ptr(T* p, const A& d);
unique_ptr(T* p, const A&& d);

-12- Requires: The expression d(p) must be well formed.

-13- If D is not an lvalue-reference type then

-14- Otherwise D is an lvalue-reference type. d must be reference-compatible with one of the constructors. If d is an rvalue, it will bind to the second constructor of this pair. That constructor must subsequently emit a diagnostic. [Note: The diagnostic could be implemented using a static_assert which assures that D is not a reference type. -- end note] Else d is an lvalue and will bind to the first constructor of this pair. The type which D references need not be CopyConstructible nor MoveConstructible. This unique_ptr will hold a D which refers to the lvalue d. [Note: D may not be an rvalue-reference type. -- end note]

-15- Postconditions: get() == p. get_deleter() returns a reference to the internally stored deleter. If D is a reference type then get_deleter() returns a reference to the lvalue d.

-16- Throws: nothing.

[Example:

D d;
unique_ptr<int, D>        p1(new int, D()); // D must be MoveConstructible
unique_ptr<int, D>        p2(new int, d);   // D must be CopyConstructible
unique_ptr<int, D&>       p3(new int, d);   // p3 holds a reference to d
unique_ptr<int, const D&> p4(new int, D()); // Error, rvalue deleter object combined
                                            //    with reference deleter type

--end example]

unique_ptr(const unique_ptr&);

-17- Declared private and left undefined to inhibit copy constructing from lvalues and const rvalues.

unique_ptr(unique_ptr&& u);

-18- Requires: If the deleter is not a reference type, construction of the deleter D from an rvalue D must not throw an exception.

-19- Effects: Constructs a unique_ptr which owns the pointer which u owns (if any). If the deleter is not a reference type, it is move constructed from u's deleter, otherwise the reference is copy constructed from u's deleter. After the construction, u no longer owns a pointer. [Note: the deleter construction can be implemented with forward<D>. -- end note]

-20- Postconditions: get() == value u.get() had before the construction. get_deleter() returns a reference to the internally stored deleter which was constructed from u.get_deleter(). If D is a reference type then get_deleter() and u.get_deleter() both reference the same lvalue deleter.

-21- Throws: nothing.

template <class U, class E> unique_ptr(const unique_ptr<U, E>&);

-22- Declared private and left undefined to inhibit copy constructing from lvalues and const rvalues.

template <class U, class E> unique_ptr(unique_ptr<U, E>&& u);

-23- Requires: If D is not a reference type, construction of the deleter D from an rvalue of type E must be well formed and not throw an exception. If D is a reference type, then E must be the same type as D (diagnostic required). U* must be implicitly convertible to T*.

-24- Effects: Constructs a unique_ptr which owns the pointer which u owns (if any). If the deleter is not a reference type, it is move constructed from u's deleter, otherwise the reference is copy constructed from u's deleter. After the construction, u no longer owns a pointer. [Note: the deleter construction can be implemented with forward<D>. -- end note]

-25- Postconditions: get() == value u.get() had before the construction, modulo any required offset adjustments resulting from the cast from U* to T*. get_deleter() returns a reference to the internally stored deleter which was constructed from u.get_deleter().

-26- Throws: nothing.

20.4.5.2.2 unique_ptr destructor

~unique_ptr();

-1- Effects: If get() == 0 there are no effects. Otherwise get_deleter()(get()).

-2- Throws: nothing.

20.4.5.2.3 unique_ptr assignment

unique_ptr& operator=(const unique_ptr&);

-1- Declared private and left undefined to inhibit copy assignment from lvalues and const rvalues.

unique_ptr& operator=(unique_ptr&& u);

-2- Requires: Assignment of the deleter D from an rvalue D must not throw an exception.

-3- Effects: reset(u.release()) followed by a move assignment from u's deleter to this deleter.

-4- Postconditions: This unique_ptr now owns the pointer which u owned, and u no longer owns it. [Note: If D is a reference type, then the referenced lvalue deleters are move assigned. -- end note]

-5- Returns: *this.

-6- Throws: nothing.

template <class U, class E> unique_ptr& operator=(const unique_ptr<U, E>&);

-7- Declared private and left undefined to inhibit copy assignment from lvalues and const rvalues of convertible unique_ptr types.

template <class U, class E> unique_ptr& operator=(unique_ptr<U, E>&& u);

-8- Requires: Assignment of the deleter D from an rvalue E must not throw an exception. U* must be implicitly convertible to T*.

-9- Effects: reset(u.release()) followed by a move assignment from u's deleter to this deleter. If either D or E is a reference type, then the referenced lvalue deleter participates in the move assignment.

-10- Postconditions: This unique_ptr now owns the pointer which u owned, and u no longer owns it.

-11- Returns: *this.

-12- Throws: nothing.

unique_ptr& operator=(unspecified-pointer-type);

-13- Assigns from the literal 0 or NULL. [Note: The unspecified-pointer-type is often implemented as a pointer to a private data member, avoiding many of the implicit conversion pitfalls. --end note]

-14- Effects: reset().

-15- Postconditions: get() == 0.

-16- Returns: *this.

-17- Throws: nothing.

20.4.5.2.4 unique_ptr observers

T& operator*() const;

-1- Requires: get() != 0.

-2- Returns: *get().

-3- Throws: nothing.

T* operator->() const;

-4- Requires: get() != 0.

-5- Returns: get().

-6- Throws: nothing.

T* get() const;

-7- Returns: The stored pointer.

-8- Throws: nothing.

deleter_type&       get_deleter();
const deleter_type& get_deleter() const;

-9- Returns: A reference to the stored deleter.

-10- Throws: nothing.

operator unspecified-bool-type() const;

-11- Returns: an unspecified value that, when used in boolean contexts, is equivalent to get() != 0.

-12- Throws: nothing.

-13- [Note: The unspecified-bool-type is often implemented as a pointer to a private data member, avoiding many of the implicit conversion pitfalls. --end note]

20.4.5.2.5 unique_ptr modifiers

T* release();

-1- Postconditions: get() == 0.

-2- Returns: the value get() had at the start of the call to release.

-3- Throws: nothing.

void reset(T* p = 0);

-4- Effects: If p == get() there are no effects. Otherwise get_deleter()(get()).

-5- Postconditions: get() == p.

-6- Throws: nothing.

void swap(unique_ptr&& u);

-7- Requires: The deleter D is Swappable and will not throw an exception under swap.

-8- Effects: The stored pointers of this and u are exchanged. The stored deleters are swap'd (unqualified).

-9- Throws: nothing.

20.4.5.3 unique_ptr for array objects with a run time length

template<class T, class D>
class unique_ptr<T[], D>
{
public:
    typedef T element_type;
    typedef D deleter_type;

    // constructors
    unique_ptr();
    explicit unique_ptr(T* p);
    unique_ptr(T* p, implementation defined d);
    unique_ptr(T* p, implementation defined d);
    unique_ptr(unique_ptr&& u);

    // destructor
    ~unique_ptr();

    // assignment
    unique_ptr& operator=(unique_ptr&& u);
    unique_ptr& operator=(unspecified-pointer-type);

    // observers
    T& operator[](size_t i) const;
    T* get() const;
    deleter_type&       get_deleter();
    const deleter_type& get_deleter() const;
    operator unspecified-bool-type() const;

    // modifiers
    T* release();
    void reset(T* p = 0);
    void swap(unique_ptr&& u);

private:
    // disable copy from lvalue
    unique_ptr(const unique_ptr&);
    unique_ptr& operator=(const unique_ptr&);
};

-1- A specialization for array types is provided with a slightly altered interface.

-2- Descriptions are provided below only for member functions that have behavior different from the primary template.

20.4.5.3.1 unique_ptr constructors

unique_ptr(T* p);
unique_ptr(T* p, implementation defined d);
unique_ptr(T* p, implementation defined d);

-1- These constructors behave the same as in the primary template except that they do not accept pointer types which are convertible to T*. [Note: One implementation technique is to create private templated overloads of these members. -- end note]

20.4.5.3.2 unique_ptr observers

T& operator[](size_t i) const;

-1- Requires: i < the size of the array to which the stored pointer points.

-2- Returns: get()[i].

-3- Throws: nothing.

20.4.5.3.3 unique_ptr modifiers

void reset(T* p = 0);

-1- Requires: Does not accept pointer types which are convertible to T* (diagnostic required). [Note: One implementation technique is to create a private templated overload. -- end note]

-2- Effects: If p == get() there are no effects. Otherwise get_deleter()(get()).

-3- Postconditions: get() == p.

-4- Throws: nothing.

20.4.5.4 unique_ptr for array objects with a compile time length

template<class T, class D, size_t N>
class unique_ptr<T[N], D>
{
public:
    typedef T element_type;
    typedef D deleter_type;
    static const size_t size = N;

    // constructors
    unique_ptr();
    explicit unique_ptr(T* p);
    unique_ptr(T* p, implementation defined d);
    unique_ptr(T* p, implementation defined d);
    unique_ptr(unique_ptr&& u);

    // destructor
    ~unique_ptr();

    // assignment
    unique_ptr& operator=(unique_ptr&& u);
    unique_ptr& operator=(unspecified-pointer-type);

    // observers
    T& operator[](size_t i) const;
    T* get() const;
    deleter_type&       get_deleter();
    const deleter_type& get_deleter() const;
    operator unspecified-bool-type() const;

    // modifiers
    T* release();
    void reset(T* p = 0);
    void swap(unique_ptr&& u);

private:
    // disable copy from lvalue
    unique_ptr(const unique_ptr&);
    unique_ptr& operator=(const unique_ptr&);
};

-1- This specialization gives the array a length known at compile time. There are three differences between this specialization, and the specialization for arrays of length not known until run time.

  1. The indexing observer behavior for i >= N is undefined for unique_ptr<T[]> and implementation defined for unique_ptr<T[N]>.
  2. The deleter is called with get_deleter()(get(), N) instead of get_deleter()(get()). Client-defined deleters may be able to make use of this extra information.
  3. The size of the array is available as a static const size_t named size.

-2- Descriptions are provided below only for member functions that have behavior different from the unique_ptr<T[]> specialization.

20.4.5.4.1 unique_ptr destructor

~unique_ptr();

-1- Effects: If get() == 0 there are no effects. Otherwise get_deleter()(get(), N).

-2- Throws: nothing.

20.4.5.4.2 unique_ptr observers

T& operator[](size_t i) const;

-1- If i >= N the behavior is implementation defined.

-2- Returns: get()[i].

-3- Throws: nothing.

20.4.5.4.3 unique_ptr modifiers

void reset(T* p = 0);

-1- Requires: Does not accept pointer types which are convertible to T* (diagnostic required). [Note: One implementation technique is to create a private templated overload. -- end note]

-2- Effects: If p == get() there are no effects. Otherwise get_deleter()(get(), N).

-3- Postconditions: get() == p.

-4- Throws: nothing.

20.4.5.5 unique_ptr specialized algorithms

template<class T, class D> void swap(unique_ptr<T,D>&  x, unique_ptr<T,D>&  y);
template<class T, class D> void swap(unique_ptr<T,D>&& x, unique_ptr<T,D>&  y);
template<class T, class D> void swap(unique_ptr<T,D>&  x, unique_ptr<T,D>&& y);

-1- Effects: Calls x.swap(y).

template<class T1, class D1, class T2, class D2>
  bool operator==(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);

-2- Returns: x.get() == y.get().

template<class T1, class D1, class T2, class D2>
  bool operator!=(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);

-3- Returns: x.get() != y.get().

template<class T1, class D1, class T2, class D2>
  bool operator <(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);

-4- Returns: x.get() < y.get().

template<class T1, class D1, class T2, class D2>
  bool operator<=(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);

-5- Returns: x.get() <= y.get().

template<class T1, class D1, class T2, class D2>
  bool operator >(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);

-6- Returns: x.get() > y.get().

template<class T1, class D1, class T2, class D2>
  bool operator>=(const unique_ptr<T1,D1>& x, const unique_ptr<T2,D2>& y);

-7- Returns: x.get() >= y.get().

Acknowledgments

Much thanks to Jonathan Turkanis for some excellent recommendations for this proposal.