shared_ptr was the latest installment in my more info then you ever wanted about 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:
Among the smart pointers
weak_ptr is sort of the odd one out since it doesn’t control the lifetime of the object referenced. It sort of sits between the level of a bare pointer and
shared_ptr in that it doesn’t affect the lifetime of an object like
shared_ptr does but, unlike a bare pointer, it does allow you to figure if the referenced object is still alive or not.
The canonical example of
weak_ptr use is to break cycles among objects that refer to each other using
shared_ptrs: object A contains a
shared_ptr to object B while object B contains a
shared_ptr to object A creating a
shared_ptr cycle and thus a memory leak since there’s no way for the reference count for either object to get to zero. If object B instead contains a
weak_ptr to object A the memory leak goes away: object A’s reference count can now go to zero and when it does the object goes away and takes object B with it. The price for this is the extra step of having to lock the
weak_ptr in object B whenever you want to access object A in one of B’s methods.
That being said, I find that I rarely use
weak_ptr in this fashion. I don’t know if it’s the nature of the software that I work on or the style in which I tend to design software, but it just seems very rare that I need to break a cycle as above. Instead I find myself mainly using
weak_ptr as an object lifetime monitor. For example: say I have an object that I’m going to use in another thread but I only want that other thread to bother doing its thing if there is still someone around that cares about the object that I’m working with. Here I’m defining cares about the object as holds a
shared_ptr to it (seems like a reasonable criteria to me). By copying a
weak_ptr into the routine in the other thread I’ll know when no one cares about the object anymore by the fact that I won’t be able to lock the
weak_ptr since all the
shared_ptr’s to the object have been dropped. Then, instead of doing a bunch of work that no one cares about the routine can just bail out early.
As a return type I think I’ve used
weak_ptr all of once, and that was the return type for make_weak, which is a special case. I can think of a theoretical need to return a
weak_ptr: you’re holding a
weak_ptr to an object and other objects want a
weak_ptr without caring about getting a
shared_ptr (probably because they just want to monitor the object’s lifetime until some later date). In this case there’s no reason to incur the overhead of locking the
weak_ptr in order to return a
shared_ptr since the return value will just be going straight into another
weak_ptr. In practice I’ve never encountered this situation, hence the theoretical label above.
The reference count object associated with an object stored in a
shared_ptr normally contains two counts: the shared and the weak counts. As you can probably imagine these are the counts of the
weak_ptrs that refer to the object that is being reference counted. When the shared count goes to zero the referenced object can be deleted, but the reference count object can’t be done away with until the weak count is zero. Otherwise we’ll be leaving all the
weak_ptrs for our object lying around with dangling pointers to the reference count effectively turning them into time-bombs waiting to detonate our program the next time some tries to lock one. Thus the same caveats that apply to
shared_ptr apply to
weak_ptr when passing them around: be wary of what’s happening to the reference count. In a way it’s worse with
weak_ptr as it lacks a move constructor making all by value passing incur reference count overhead. So you will want to pass
const references instead of by value the majority of the time.
The weak count is thread-safe in the same way that the shared count is, but you do need to be careful of using the same
weak_ptr instance in multiple threads similar to how you have to treat
shared_ptr. Also you should treat creating a
weak_ptr from a
shared_ptr the same as copying the
shared_ptr when it comes to thread synchronization.
The only reason to hold a
weak_ptr as a member variable is in the two cases given above: breaking a reference cycle or when you want to monitor an object’s lifetime but not participate in prolonging it. It’ll normally be obvious when one of those situations has arisen, sometimes less so in the cycle case. The rest of the time you’ll probably want
So that’s it for smart pointers, at least the ones I’m interested in talking about.
auto_ptr is dead, just forget about it and hope you don’t run into it in legacy code.
boost::scoped_ptr isn’t quite as dead, but there’s no real reason to prefer it over
unique_ptr. Should you encounter it in the wild you can probably just leave it alone and things will be fine. In the case that things aren’t fine (a need to move the object from one
scoped_ptr to another has probably arisen) you should just be able to replace it with
unique_ptr and move along.
Next time I’ll get into
boost::optional which is sort of like a pointer but sort of not. Its use is, well, optional but it turns out to be quite useful in many cases and in those cases it has advantages over using a
unique_ptr (it’s closest pointer analog).