GotW #103: Smart Pointers, Part 1 (Difficulty: 3/10)

JG Question

1. When should you use shared_ptr vs. unique_ptr? List as many considerations as you can.

 

Guru Questions

2. Why should you always use make_shared to allocate objects whose lifetimes will be managed by shared_ptr? Explain.

3. What’s the deal with auto_ptr?

12 thoughts on “GotW #103: Smart Pointers, Part 1 (Difficulty: 3/10)

  1. 1) StackOverflow: Which kind of pointer do I use when? :)

    2) Not always. make_shared doesn’t allow to specify your own deleter. However, if there is no need for one, then there are indeed good reasons to use it. For one, make_shared compresses the allocations into a single one, where a shared_ptr<X&rt; p(new X); would need two allocations: one for the X object, one for the reference-count block used by the shared_ptr.

    3) auto_ptr has broken copy semantics. The copies actually moves the auto_ptr, meaning it transfers the ownership on copy. As such, it’s unusable in standard containers and even deprecated by the C++11 standard.

  2. The `unique_ptr`:
    – Ownership transfer. Factories should ideally return `unique_ptr` instead of raw pointers.
    – Safely pass an object between threads (only one thread can access it at a time).
    – Can be used as a holder for the impl object in the pimpl idiom.
    – Can be used in containers thanks to move semantics. But it’s not very flexible.
    – Can be used for the similar purposes as `boost::scoped_ptr`.

    The `shared_ptr`:
    – Shared ownership. I usually prefer single ownership though.
    – Safely store pointers in a container. More flexible than `unique_ptr` in this regard.
    – parent-child relations can be modeled using `shared_ptr` and `weak_ptr`.
    – Accepts a `free`-function as a second constructor argument.
    – It generates a ‘free’-function based on the type provided in the constructor. So `shared_ptr ptr(new SubClass());` is safe even if `Base` does not have a virtual destructor.

    Concerning `make_shared`:
    – Safe alternative to unnamed `shared_ptr` temporaries (see [documentation of boost::shared_ptr](http://www.boost.org/doc/libs/1_48_0/libs/smart_ptr/shared_ptr.htm#BestPractices) ).
    – It’s a nicer and compacter syntax.

    Concerning `auto_ptr`:
    – Has similar use as `unique_ptr` (ownership transfer, passing between threads) or `boost::scoped_ptr`.
    – Not safe to use in containers.
    – Dangerous to use for member variables in classes do not have a user-defined copy constructor and assignment operator. Even though the class obeys the rule-of-three, it is not safe to copy because it leaves the original invalidated.

    With `auto_ptr` it’s easy to shoot yourself in the foot by passing it to a function and then still try to use it:

    std::auto_ptr r = foo();
    bar(r); // This looks like a regular function call.
    std::cout <value(); // Ouch! I forgot the object is no longer accessible!

    With `unique_ptr` you must explicitly move the pointer by calling `std::move`. This makes me less likely to forget that I can no longer access it:

    std::unique_ptr ptr = foo();
    bar(std::move(ptr)); // alerts me that the object is no longer accessible

  3. 1. Whenever the ownership of a resource (including heap-allocated memory) is well defined during the whole lifetime of the object, you should use unique_ptr. Ownership means responsibility for releasing the resource. Ownership may be passed by _moving_ unique_ptr.

    Only if you can’t clearly assign the owner to the resource should you use shared_ptr. This happens when access to a resource is shared between objects whose lifetimes cannot be predicted. shared_ptr implements the policy of “last to leave the room turns off the lights” (releases the resource).

    In a multithreaded context, unique_ptr is the fastest and safest way of moving data between threads, as long as there are no stray aliases left behind. Uniqueness means that only one thread at a time has access to the resource and no additional synchronization is required. This is not true with shared_ptr, which facilitates sharing of resources between threads. The object in shared_ptr must implement its own synchronization. What shared_ptr provides is thread-safe reference counting, but at a non-trivial cost.

    2. make_shared is a substantial optimization. Normally a shared_ptr needs a separate heap object for storing the reference count. When shared_ptr is created using make_shared, the memory for the object and the reference count are allocated together. Besides saving one allocation, this also improves locality.

    3. auto_ptr was a hack that is now deprecated. Move semantics and rvalue references allow clean and efficient implementation of unique_ptr. The major danger with auto_ptr is that it could be quietly passed using its copy constructor, which would invalidate the source auto_ptr. If that source auto_ptr was subsequently accessed, it would result in a fault. unique_ptr, on the other hand, cannot be passed quietly, unless it’s an rvalue. The programmer has to explicitly call std::move to pass an lvalue unique_ptr. That makes for a safer code.

  4. 1: When should you use shared_ptr vs. unique_ptr? List as many considerations as you can.

    unique_ptr should be used for situations where a function or object needs to allocate something, but does not need to share ownership of that allocated object with some other object or function. The object/function will directly control the lifetime of the object.

    unique_ptr should be used in situations where ownership can be *given* to another object/function. That is, after the function call, the current owner no longer has access to it anymore.

    shared_ptr should be used in situations where multiple functions/objects can *simultaneously* claim ownership to the same allocated resource.

    shared_ptr should also be used in situations where other functions/objects may want to weakly reference an object. This is useful for objects that need to monitor other objects, but don’t fully claim ownership over the lifetime of that object. The object with the weak reference can check to see if the object still exists, but it does not prevent it from being destroyed.

    2: Why should you always use make_shared to allocate objects whose lifetimes will be managed by shared_ptr? Explain.

    shared_ptr offers a number of features. The main one is the ability for multiple copies of the shared_ptr to all claim ownership of the same object. In order to do this, shared_ptr requires a special control block of memory that stores a count of references to the managed pointer. This block is separate from the actual pointer being owned.

    Consider the following.

    auto variable = shared_ptr(new Typename);

    Two memory allocations happen. `new Typename` allocates the object that will be owned. But the constructor of the shared_ptr also allocates the control block which contains the reference count. That is two separate allocations.

    Since memory allocation is not exactly a lightweight activity, it is best to minimize it. Because make_shared does the construction of the type, it has the ability to combine the memory for the object and the control block into one allocation.

    Therefore, where *possible*, it is best to use make_shared. However, it is not always possible. It cannot be used for managing memory from a source that is not a C++ object, such as an opaque pointer returned from a C-style API.

    allocate_shared should be used in circumstances where a special memory allocator is needed.

    So the question itself presupposes a conclusion that is incorrect, since make_shared cannot replace *all* uses of shared_ptr creation.

    3: What’s the deal with auto_ptr?

    auto_ptr was an attempt to replicate the effective functionality of unique_ptr using a language that couldn’t express true move semantics. Its copy constructor actually *moved* the ownership rather than copying it.

    One can make reasonably safe use of auto_ptr if one is careful. However, it cannot be used in a standard libary container due to its “copying” semantics. Standard containers that copy their elements at any time could easily break any stored auto_ptr. Something similar can happen for storing auto_ptr in an object without a specialized copy constructor/assignment operator.

    Since unique_ptr uses C++11 features to explicitly disallow copying, and C++11 standard containers can store non-copyable objects via use of move constructors, auto_ptr is effectiveless useless. It was therefore deprecated in C++11.

  5. I’d like to clarify one point: the distinction between strong and weak pointers.

    When using unique_ptr, there is only one owner of the resource at a time. However, unique_ptr may grant non-owning access to its resource. This is very important when, e.g., you want to call a library function that takes a pointer to your resource:

    libFun(Foo *);
    unique_ptr foo(new Foo);
    libFun(foo.get());

    By “library” I mean any reusable code. It makes very little sense to have a library function that operates of Foo, but has no interest in the ownership of Foo, to have this signature:

    libFun(unique_ptr & foo);

    The pointer passed to libFun(Foo*) is a transient alias, or a “weak pointer” to the resource. It’s a bad idea to let the function or a data structure store such a transient alias for two reasons: (1) The alias might become invalid when the owner is destroyed, (2) such aliases might appear in a different thread than the owner’s. In the latter case, thread safety of a unique_ptr is compromised. A unique_ptr is thread-safe only if there are no aliases to it or to its contents (transitively), and there are no aliases inside its contents to data that’s accessible from the outside. In general, a unique_ptr defines its own cluster of interlinked objects that have no connection to the outside (incoming or outgoing references).

    When using shared_ptr, many copies of it might coexist in the program at the same time. It’s still not a good idea to store aliases to the resource it’s protecting or to its transitive contents.

    shared_ptr implements a simple form of garbage collection — reference counting. The problem with reference counting is that it doesn’t deal well with cycles. If you have a graph data structure that contains back pointers, you can’t turn them into shared_ptrs without risking cycles. The semi-safe way to implement back pointers in a shared_ptr-based data structure is to use weak_ptr. A weak_ptr doesn’t take part in reference counting, so it doesn’t create non-collectible cycles, but it can be (temporarily) converted into a shared_ptr to gain access to the resource. (Of course, if the resource has already been collected, this conversion will not work and it will result in an exception.) weak_ptrs are used, for instance, in shareable doubly-linked lists or trees with parent pointers.

  6. To add to the much belaboured point 2:

    One further situation in which you don’t use `make_shared` is when constructing a shared pointer *from* an existing unique pointer, possibly with a non-default deleter. The resulting shared pointer will have to be one of the type with separate refcontrol block (including the deleter information) and distinct dynamic resource (which already comes fully furnished from the unique pointer).

    Interestingly enough, the construction-from-unique&& doesn’t appear to allow you to specify an allocator.

  7. @Kerrek SB: The question was not about constructing shared pointer but about allocating a memory for and constructing an object that is to be managed by shared_ptr. When the object is already constructed there is no use for the make_shared utility.

    On the other hand in my work I’ve recognized two problems with make_shared:
    1. You cannot use make_shared with classes that have protected constructor. It is essential when the class have it’s own “create” factory performing some additional work and we want to disallow the means for creating the object outside of the factory. Standard does not specify with who to be befriended (and even if, it would create a loophole) and there is no base class to inherit from that would do the work effectively.
    2. When weak_ptr outlives shared_ptr the memory that have been occupied by the object can’t be freed until all weak pointers will be destroyed.

  8. 3. What’s the deal with auto_ptr? — Apart from it’s standard set of problems, auto_ptr is completely broken in VC8 / VS2005, which unfortunately is still my compiler at work.

    In VC8 the statement: auto_ptr(T) = new T(); // will compile and crash at runtime. Note that I’ve replaced the angle brackets for posting purposes.

  9. I can’t add anything more that has been said above to the answers regarding the actual question. One point though, as an addition to Waldemar Pawlaszek’s comment. There are situations when you can’t use the full power of currently standardized smart pointers: When you need to pass resources between independently compiled components. “Independent” means possibly compiled with different versions of std implementations (with different implementation of std::shared_ptr & friends) and/or linked against different run-time libraries. From such a component, you typically do not export the definition of T at all, so there is no way to construct with the aid of make_shared. That’s when (at least to some extent) boost::intrusive_ptr jumps in, leaving the resource lifetime management completely up to the implementation.

  10. (Xeo gave the purist auto_ptr answer. So here my purist answer on shared_ptr vs. unique_ptr)

    Use shared_ptr to allow for multiple isochronal owners, none of which shall have exclusive right to delete.

    Use unique_ptr to allow for exactly one isochronal owner.

Comments are closed.