C++11: using unique_ptr with standard library containers

June 20th, 2012 at 8:46 pm

Before C++11, the only "smart" pointer available in the standard C++ library was auto_ptr. Alas, auto_ptr isn’t very smart. It has very problematic copy semantics that make it difficult to use in several important scenarios. In particular, auto_ptr can not be used with standard containers and algorithms!

To quote from Herb Sutter:

auto_ptr is most charitably characterized as a valiant attempt to create a unique_ptr before C++ had move semantics.

auto_ptr is now deprecated, and should not be used in new code. When you get a chance, try doing a global search-and-replace of auto_ptr to unique_ptr in your code base

So what is this unique_ptr thing, and what can it be used for?

Basic capabilities

To put it simply, unique_ptr should be the default smart pointer used by new C++ code, replacing "raw" pointers as much as possible. unique_ptr cleanly represents the single ownership idiom – it cannot be copied and assigned, and it cleans up the pointed object when it’s destructed.

Here’s some code to demonstrate this [1]:

#include <iostream>
#include <cstdlib>
#include <memory>
using namespace std;

struct Foo {
    Foo() {cerr << "Foo [" << this << "] constructed\n";}
    virtual ~Foo() {cerr << "Foo [" << this << "] destructed\n";}
};

int main(int argc, char** argv) {

    // .. some code
    {
        unique_ptr<Foo> fp(new Foo());

        unique_ptr<Foo> fp2(fp);    // ERROR! can't copy unique_ptr
        unique_ptr<Foo> fp3;
        fp3 = fp;                   // ERROR! can't assign unique_ptr

        cerr << "Exiting scope\n";
    } // fp will be destroyed, and will destruct the pointed object

    return 0;
}

The lines marked with the ERROR! comment won’t actually compile. The compiler will complain saying something like:

error: use of deleted function
 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&)

If these two lines are commented out, the code will print:

Foo [0x845010] constructed
Exiting scope
Foo [0x845010] destructed

In addition to managing the pointed object’s lifetime, unique_ptr provides the other expected capabilities of a smart pointer: it overloads operator* and operator->, provides a means to obtain the raw pointer (get), to relinquish control of the pointed object (release), and to replace the object it manages (reset). It also lets you customize the way the pointed object is deleted (if you don’t want it to be the default delete operator), and has some other niceties – just consult your favorite C++ reference.

What about sources and sinks?

In this article I want to focus not on the grocery list of unique_ptr‘s features, but its interesting move semantics. Specifically, given that unique_ptr forbids copying and assignment, one may wonder how it can fit in the source and sink idiom which is so useful for smart pointers.

In other words, we’d like this to work:

// source creates a Foo object, wraps it in a smart pointer for safety
// and provides the result to the caller, giving it the ownership of the
// object in the process.
unique_ptr<Foo> source();

// sink gets a Foo object wrapped in a smart pointer for safety. It also
// assumes ownership of the provided object.
void sink(unique_ptr<Foo> p);

And in C++11, it does! Even though unique_ptr can’t be copied, it can be moved. Move semantics are a perfect match for unique_ptr – the two concepts reinforce each other. With move semantics, unique_ptr is both safe and efficient. Here’s some code to demonstrate this:

#include <iostream>
#include <cstdlib>
#include <memory>
using namespace std;

struct Foo {
    Foo() {cerr << "Foo [" << this << "] constructed\n";}
    virtual ~Foo() {cerr << "Foo [" << this << "] destructed\n";}
};

void sink(unique_ptr<Foo> p) {
    cerr << "Sink owns Foo [" << p.get() << "]\n";
}

unique_ptr<Foo> source() {
    cerr << "Creating Foo in source\n";
    return unique_ptr<Foo>(new Foo);
}

int main(int argc, char** argv) {
    cerr << "Calling source\n";
    unique_ptr<Foo> pmain = source();  // Can also be written as
                                       // auto pmain = source();

    cerr << "Now pmain owns Foo [" << pmain.get() << "]\n";
    cerr << "Passing it to sink\n";
    sink(pmain);                    // ERROR! can't copy unique_ptr
    sink(move(pmain));              // OK: can move it!

    cerr << "Main done\n";
    return 0;
}

Again, there’s a line marked with ERROR! here – it demonstrates once again that a unique_ptr can’t be copied. However, it can be explicitly moved, as the next line shows [2]. When the erroneous line is commented out, this code prints:

Calling source
Creating Foo in source
Foo [0x1767010] constructed
Now pmain owns Foo [0x1767010]
Passing it to sink
Sink owns Foo [0x1767010]
Foo [0x1767010] destructed
Main done

Note how cleanly the ownership is being passed between the functions in this code. At each point in time, only a single unique_ptr owns the pointed Foo object. Moreover, this is efficient – the actual pointed object only gets constructed once and destructed once.

Containers – motivation

So unique_ptr is a useful single-ownership smart pointer. But what makes it really shine (especially when compared to auto_ptr) is that it can be used in standard containers.

Why is it so important to be able to place smart pointers into containers? Because holding objects by value is sometimes very expensive. Containers, especially when coupled with algorithms, tend to move objects around. Large objects are expensive to copy, hence we’d like to keep pointers to objects inside containers instead.

What follows is a very simplistic example that demonstrates this. It shows how much more expensive it is to sort a vector of large objects that are stored by value, than it is when they’re stored by pointer [3].

First, let’s create a synthetic "large" object that has well defined ordering properties by some numeric ID:

struct SomeLargeData {
    SomeLargeData(int id_)
        : id(id_)
    {}
    int id;
    int arr[100];
};

We also need a function to compare two such objects. Actually, we need two – one for a container that holds object by value, and another for the by-pointer version:

bool compare_by_value(const SomeLargeData& a, const SomeLargeData& b) {
    return a.id < b.id;
}

bool compare_by_ptr(const SomeLargeData* a, const SomeLargeData* b) {
    return a->id < b->id;
}

Let’s now create two vectors and populate them with random objects:

vector<SomeLargeData> vec_byval;
vector<SomeLargeData*> vec_byptr;

for (int i = 0; i < n; ++i) {
    int id = rand() % 500000;
    vec_byval.push_back(SomeLargeData(id));
    vec_byptr.push_back(new SomeLargeData(id));
}

Finally, we’ll sort the two vectors with the standard sort algorithm, and measure the runtime for some large n:

sort(vec_byval.begin(), vec_byval.end(), compare_by_value);
sort(vec_byptr.begin(), vec_byptr.end(), compare_by_ptr);

The timing results I get are quite consistent – the by-pointer sorting is 2-3x faster than the by-value sorting [4]. That’s a very significant difference, and it’s all due to the copying sort has to do for moving the objects around inside the container.

So holding objects of non-trivial size inside standard containers is not a good idea in terms of performance. But holding raw pointers to them is also not so great, because of all the safety issues that come with raw pointers. The container can’t own the pointed objects because its destructor will just "destruct" the pointer, which does nothing. So the calling code has to own the actual objects which are being shuffled around by the container. Add exceptions and/or early returns to the mix, and this is a recipe for memory leaks or even worse problems.

What we’d really like to do is let our objects be managed by a smart pointer and put that into a container. This would guarantee a clean ownership strategy – the container destroys its contents when it gets destroyed itself – just the way it should be. This is why unique_ptr is so exciting.

Containers of unique_ptr

Adapting the by-pointer version of the code above to hold unique_ptr is very simple. First, we need another comparison function:

bool compare_by_uniqptr(const unique_ptr<SomeLargeData>& a,
                        const unique_ptr<SomeLargeData>& b) {
    return a->id < b->id;
}

And then we just need to create the vector, populate it and then sort it, similarly to the way we’ve done for the other vectors:

vector<unique_ptr<SomeLargeData>> vec_byuniqptr;

for (int i = 0; i < n; ++i) {
    int id = rand() % 500000;
    // ...
    vec_byuniqptr.push_back(
        unique_ptr<SomeLargeData>(new SomeLargeData(id)));
}

sort(vec_byuniqptr.begin(), vec_byuniqptr.end(), compare_by_uniqptr);

That’s it! And the performance? Almost identical to the by-pointer version (I measured differences of 1-5%, depending on the data).

What about shared pointers?

Another smart pointer C++11 brings with it is the shared_ptr/weak_ptr pair, implementing a reference-counted approach to shared ownership. While much more flexible than unique_ptr, shared_ptr is slower and consumes more memory; managing the reference count is not free [5].

Which one to use depends on your exact needs, but I agree with Herb Sutter’s proposal of using unique_ptr by default and switching to shared_ptr if the need arises.

In addition, it is my personal opinion that preferring unique_ptr imposes a certain memory management discipline on the code, since you know at each point exactly who owns what. Shared pointers give you a sense of security you can over-use and end up with reference leaks, which are tricky to debug (just like when writing Python C extension code). Moreover, shared pointers signal the intention of APIs less clearly than owning pointers. When some factory returns a shared pointer, does it mean it keeps a reference to the object too? With an owning pointer, the API is self documenting (source returns a unique_ptr? then source is for sure giving away ownership). With a shared pointer, it does not, and need external documentation to clarify.

Conclusion

I have mentioned how rvalue references and move semantics can make code more efficient with C++11. unique_ptr is another great example that makes me want to use a C++11-capable compiler as soon as possible.

unique_ptr provides an excellent mix of efficiency and safe memory management. IMHO it’s a great example of how several ideas in language design interact to create a whole that is larger than its parts.

http://eli.thegreenplace.net/wp-content/uploads/hline.jpg

[1] C++11 support in compilers and standard library implementations varies. To make all the code in this article work, I installed the latest gcc (4.7.1) from source on my Ubuntu box. It didn’t work with 4.5, I don’t know about 4.6.
[2] It can also be taken from an rvalue: sink(unique_ptr<Foo>(new Foo)) would work, because rvalue references can be moved directly.
[3] I don’t claim this is good design – it’s just a synthetic example created to demonstrate a point.
[4] The speedup grows as the size of the object grows. Increasing the arr member to hold 1000 integers makes the speedup 10x.
[5] For the sorting benchmark demonstrated in this article shared_ptr is about 10% slower than unique_ptr. As for size, while the size of unique_ptr is exactly the size of a raw pointer, shared_ptr is about twice as large.

Related posts:

  1. making sense of pointers
  2. On parsing the C standard library headers
  3. Pointers to arrays in C
  4. void* and casts, in C and C++
  5. pyelftools – Python library for parsing ELF and DWARF

11 Responses to “C++11: using unique_ptr with standard library containers”

  1. Elazar LeibovichNo Gravatar Says:

    I’m not sure I agree with the spirit of the article:

    1) Bare pointer have its uses. As you said, unique pointer means passing ownership, bare pointer means you can use it, but you’re not allowed to keep it, and you’re not responsible for disposing it.

    The good rule of a thumb IMHO, is not “don’t use bare pointers”, but “never write delete in new code”.

    2) The best practice is not “build huge object and fix it by unique pointers”, but “build sensible objects and pass them by value”. So for instance SomeLargeData will hold a vector instead of plain array. Then, you can simply store it in the vector, and all will work well.

    I’m not saying of course that unique_ptr is not useful, on the contrary, it’s immensely useful, but it is not the only way to move things efficiently in C++11.

  2. elibenNo Gravatar Says:

    Elazar,

    Not sure what exactly you don’t agree with. It didn’t say to never use raw pointers – I agree they have their uses, especially for “borrowed” references in some cases. Also, I explicitly said to take the container example with a grain of salt as it was synthetically built to demonstrate the point. Yes, it is best to avoid huge objects, but that’s not always possible. Even if the object isn’t just a holder of some array/vector, it may have a few fields that make it considerably larger than a pointer. In such cases, sorting via a pointer makes sense.

  3. Elazar LeibovichNo Gravatar Says:

    1) What I bothered me was “…replacing “raw” pointers as much as possible.”, but I guess we both agree that unique_ptr is the default over shared_ptr.

    2) I don’t think a few fields using unique_ptr would increase performance. A vector of class with 6 pointers ran as fast as its unique_ptr brother:

    http://ideone.com/shjPr

  4. fanNo Gravatar Says:

    Can’t we get the best of all worlds, by doing this:

    vector<SomeLargeData> vec_byval;
    vector<SomeLargeData*> vec_byptr;
    
    for (int i = 0; i < n; ++i) {
        int id = rand() % 500000;
        vec_byval.push_back(SomeLargeData(id));
        vec_byptr.push_back(&vec_byval.back());
    }
    
    sort(vec_byptr.begin(), vec_byptr.end(), compare_by_ptr);

    This gives us fast by-pointer sorting AND ownership of the pointed objects. So I don’t get the excitement about unique_ptr.

  5. j_random_hackerNo Gravatar Says:

    Interesting article, thanks, but I’m wondering about one thing: What guarantee do we have that sort() can sort unique_ptrs? Consider that any algorithm that can produce multiple copies of a given element (e.g. fill()) is necessarily incompatible with unique_ptr element types; only algorithms that result in at most 1 copy of each original element can possibly be implemented compatibly. The fact that sorting a container meets the latter criterion (it leaves exactly 1 copy of each element) means that it’s clearly *possible* to implement sort() so that it can work on unique_ptr elements (e.g. it *could* be implemented using only swap()s on elements, never assignments), but equally it’s possible that it’s implemented incompatibly (e.g. it might internally use “vanilla” assignments or copy constructor calls on the element type). In fact it seems likely that it would be implemented these incompatible calls, since doing so would be (a constant factor) faster in some cases!

    So: Does the standard guarantee that sort() (and other similar algorithms, like remove()) will be able to handle unique_ptrs? Or was it just a lucky fluke of the particular C++ Standard Library implementation you used that your example code compiled and ran?

    Thanks!

  6. elibenNo Gravatar Says:

    j_random_hacker,

    I assume that the C++11 standard library is required to implement appropriate move semantics in relevant algorithms.

  7. PSNo Gravatar Says:

    I wonder how,

    unique_ptr source() works, Shouldn’t it be unique_ptr& source(). In the former, it is a return by value and I wonder how this works as you described two lines later that passing pmain to sink in sink(pmain) doesn’t work as it can’t be copied as it is pass by value.

  8. JesusNo Gravatar Says:

    PS, implicit move semantics

  9. westforkNo Gravatar Says:

    Hi

    Nice article. But my results are different from what you wrote.
    I implemented your code using gcc 4.7 (g++ (Ubuntu/Linaro 4.7.2-4precise1) 4.7.2) on my desktop (OS xubuntu 12.04, Intel(R) Core(TM)2 Duo CPU, E8400 @ 3.00GHz).
    I build a vector of 2010000 containing SomeLargeData objects, raw pointers to SomeLargeData objects and unique-pointers to SomeLargeData objects. The results are that raw pointer container is the faster to sort (as expected) but the container with the unique pointers is the slower!!!
    Here a dump of my tests

    By value test
    TIMESTAMP-START 11:18:23:127277 (ms ~ 40703127)
    TIMESTAMP-END 11:18:27:378556 (ms ~ 40707378)
    ELAPSED TIME (ms) 4251
    By pointer test
    TIMESTAMP-START 11:18:27:378658 (ms ~ 40707378)
    TIMESTAMP-END 11:18:29:841735 (ms ~ 40709841)
    ELAPSED TIME (ms) 2463
    By unique_pointer test
    TIMESTAMP-START 11:18:29:841816 (ms ~ 40709841)
    TIMESTAMP-END 11:18:35:674064 (ms ~ 40715674)
    ELAPSED TIME (ms) 5833

    What do you think? The overhead of the unique-ptr over the raw pointer is so heavy?

    Regards
    westfork

  10. elibenNo Gravatar Says:

    westfork,

    Strange indeed. I would try with a different compiler, maybe the specific version you have has a flawed implementation of unique_ptr.

  11. SqeakyNo Gravatar Says:

    @Fan The first time the value vector grows it will invalidate all the pointers in the pointer vector. Since it never tells you when this happens, but can happen on any insertion, you have to assume that the pointer is invalid after the next insertion.

    @Westfork I would guess that the implementation just hasn’t had enough time to mature yet. There must be at least as much over with the unique_ptr as raw pointers, but double does seem excessive. Did you remember to delete in the raw pointer example?

Leave a Reply

To post code with preserved formatting, enclose it in `backticks` (even multiple lines)