In general, code bases using boost, or C++11, seem to gravitate toward shared_ptr “everywhere”, or developers avoid shared pointer altogether. When developers come from the Java/C# world, shared_ptr often stands in for garbage collection. Other develops will bash the performance hit of shared_ptr and rail against the use everywhere crowd. Neither of these is a good thing. Let’s tackle shared_ptr issues one at a time.
Performance
Multiple old school C/C++ developers see a shared_ptr and immediately think – extra clock cycles! Compared to a stack based variable, this is true – dynamic memory allocation is an order of magnitude slower than simply reserving space on the stack. However, if the object will have a lifespan to justify being placed in dynamic memory, things get more interesting.
shared_ptr costs in 2 ways - shared pointer operations(construct, copy, move) and additional overhead of pointer dereference.
When dereferencing, shared_ptr will add an extra dereference cost. With disabled optimization/debug, it’s possible an extra function call will be added as well. However, there are no memory or locking barriers to be concerned with.
shared_ptr penalizes performance during assignment and destruction. Assignment and modification will generally result in an interlocked increment/decrement operation. These are substantially less expensive than heavy weight locks, but still result in a memory barrier. Still, with careful stewardship, these operations can be minimized and limited to areas with significant preexisting costs. (The overhead of a shared_ptr destructor is substantially less than the delete operation it might invoke.)
TL;DR std::shared_ptr causes a small performance hit on dereference, and is more expensive to copy or move.
Ownership
The most common mistake in shared_ptr use is not realizing that pointer ownership remains important. For example, let’s say we have “car” and “garage” objects. The “car” object has a concept of “location”, and the “garage” object has a concept of “contents”. A shared_ptr here would result directly in memory leakage.
We can resolve the circular link by defining a clear ownership rule. In this case, perhaps the “location” owns the car – we transfer the “car” object to using a std::weak_ptr and break the ownership chain.
However, now that we have a clear ownership chain, we can ask another pertinent question – does the existence of a shared_ptr make sense at all? If the “garage” owns the car, using a naked pointer (or reference), might be a better solution.
Exception Safety
Wrapping elements in a shared_ptr does help improvement exception safety. However, there are multiple other wrappers that will accomplish the same goal (std::unique_ptr), not to mention the safest route of simply using the stack for variable storage.
Conclusion
When does a shared_ptr make sense? For me, this has come down to a single, simple question – “Does the object need multiple owners”? A followup question – “Does this object have any life cycle needs that make multiple owners sensible”? Especially when dealing with asynchronous IO and callbacks, ownership of an object and life cycle guarantees often resolve to multiple ownership.
In cases with dynamic and shared ownership, the shared_ptr is difficult to beat – well tested, known thread safety, and nicely optimized.