Pointing the Way Redux, Part 4: unique_ptr

So far in my series on what reference types to use when we’ve seen values, references and bare pointers. The first makes a strong statement about ownership when used as a return value: returning by value tells the caller that they own the object being returned. The other two are wishy-washy about ownership, but in modern C++ they should be interpreted as “you can use this, but I’m not giving you ownership”. Smart pointers are not wisy-washy in this way, they very clearly state what the ownership semantics of the referenced object are. The simplest of the smart pointers is unique_ptr; as the unique in its name implies there can be only one unique_ptr referencing a given object. So, similar to value semantics, by returning a unique_ptr you’re telling the caller: “I’m giving you this and you are the only owner”. Unlike returning by value however, returning a unique_ptr is safe to do with polymorphic objects – there is no danger of slicing. Of the smart pointers, unique_ptr is also the most flexible as a return type since you can transfer an object from a unique_ptr to a shared_ptr while going the other way (shared_ptr to unique_ptr) is not possible.

When returning a unique_ptr from a function your thread-safety concerns are mostly tied up in what the source of the object is. If you created the object in your function then there’s normally no thread-safety to worry about: the object isn’t accessible in any other threads yet so you don’t have to worry about synchronization – as long as you haven’t passed a bare pointer of reference to another thread (and that is a really bad idea unless the thread is going to finish before you return). On the other hand, if it’s another unique_ptr (e.g. a member variable) then you have to be sure that you’re acessing that member variable in a thread-safe manner. And if you’ve been giving out bare pointers or references to the object (see below) then you’ve got the usual concerns about thread-safety that apply when using bare pointers and references. And really, if you’re giving out bare pointers or references then you can’t safely transfer the object out of the member variable ever, unless you can somehow guarantee that there aren’t any outstanding references when you make the transfer.

When you take a unique_ptr as an argument you’re saying that you are taking ownership of the object. And because unique_ptr is move-only there’s no way to take one as an arguement without transferring ownership; unless you do something weird like taking a const reference to a unique_ptr so that you can use the object being pointed at. In this case you should probably be taking a reference to the object that the unique_ptr points at instead (or a bare pointer if you require nullability). That makes your intentions clear and also gives the caller more flexibility in what can be passed to your function. If you are transferring ownership then take the unique_ptr by value or by r-value reference: since unique_ptr is not copyable they are equivalent and both communicate to the caller that ownership is being transferred. If the final resting place of the argument is a shared_ptr then you should be taking a shared_ptr instead of a unique_ptr as doing so will provide more flexibility to the caller: if you’re storing the object in a shared_ptr chances are that that type of object is stored in a shared_ptr elsewhere and since objects can never be removed from shared_ptrs you unnescessarily limit what can be passed to your function by taking a unique_ptr(i.e. no one will be able to pass in an object that’s already stored in a shared_ptr). Always remember: it’s possible to go from a unique_ptr to a shared_ptr, but not vice-versa.

As a member variable unique_ptr is second in line only to storing the value itself. When you’ve got a polymorphic object to store or you need nullability then unique_ptr jumps to the head of the line. Nullability can also be covered by boost::optional but note that boost::optional always takes up storage for the size of the object, even when there is no object being stored, while unique_ptr is only the size of a pointer when there is no object to store – the trade-off being that boost::optional doesn’t require a dynamic memory allocation when the object is created and the object stored is a contiguous part of the parent object instead of being allocated elsewhere on the heap. I’ll have more to say on boost::optional in a later post. You could also use a shared_ptr wherever a unique_ptr is called for, but by doing so you introduce quite a bit of overhead compared to using a unique_ptr.

Direct access to an object stored in a unique_ptr member variable – should you decide to provide it and you proabably shouldn’t – should be done by returning references or bare pointers to the object, not by returning references to the unique_ptr itself. That would reveal implementation details unescessarily and confuse the caller about what was going on with the object’s ownership. Should you decide to provide direct access to the object via bare pointers or references then, as explained above, you have onerous thread-safety restrictions on how you use the unique_ptr. Specifically: you can never change what the unique_ptr points at. Most of the time the better strategy is to not expose the object directly and instead provide methods in the object that contains the unique_ptr that use the pointed to object. By doing this you can synchronize everything so that should you need to change what the unique_ptr points at you can safely do so. If you do need to provide direct access to the pointed to object then most likely your member variable should be a shared_ptr instead, about which I’ll have more to say in a future post (should be the next one).

Advertisements

5 comments

  1. […] far we’ve covered Values, references, bare pointers, and unique_ptr in my more info then you ever wanted about which reference to use when series. Today we’ll move […]

  2. […] which reference to use when series that has also covered values, references, bare pointers, and unique_ptr. This time I’ll be covering the last of the smart pointer trifecta: […]

  3. […] to use when series – that has also covered values, references, bare pointers, shared_ptr and unique_ptr – I covered the last of the smart pointers: weak_ptr. This time we’ll cap off the series with […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: