Understanding Smart Pointers
Polymorphism tells you that the code needs a pointer or reference. The number of owners tells you which smart pointer expresses that need. Keep these two questions separate.
The Tempting Rule
Once you learn that unique_ptr and shared_ptr exist, a simple rule seems to follow: whenever a program needs to manage a resource, wrap it in a smart pointer, and the design is sound. The rule feels reasonable. Smart pointers do handle cleanup automatically, and they let a program follow a policy of never writing a bare allocation that someone must remember to release. This article tests that rule and shows how to replace it with a better way of choosing.
What do you think happens?
Is the rule 'wrap every resource in a smart pointer' the right way to choose a design?
Reveal answer
No. A smart pointer is a deliberate second choice, made after a container or other resource handle has been ruled out.
The source ranks smart pointers below containers and similar types. The deciding question is not whether a resource needs cleanup, but whether the code truly needs pointer semantics. Keep this answer in mind as the rest of the article traces why.
Smart Pointers as a Second Choice
The source makes a claim about ranking, not just about what smart pointers can do. unique_ptr and shared_ptr together can support a complete no naked new policy for many programs. In other words, every allocation can be owned by a smart pointer rather than managed by hand. That is a real capability. But these types are still conceptually pointers, and that places them below containers and similar types. Those types manage their resources at a higher conceptual level and operate in terms of the resource itself. A vector manages a sequence of elements. A thread manages a running activity.
This is why the source describes smart pointers as a second choice for resource management. The ordering is about abstraction level. A container or resource handle lets you talk about the resource at the level you care about, and it hides the pointer. A smart pointer makes you talk about who refers to an object and when it is released. You reach for the second only when that pointer-level conversation is actually required.
| Tool | What it manages | When to reach for it |
|---|---|---|
| Container or other resource handle | The resource itself, such as a sequence of elements or a running activity | First choice. Use it whenever its operations fit the resource. |
| Smart pointer (unique_ptr or shared_ptr) | An object through a pointer, including who owns it and when it is released | Second choice. Use it when pointer semantics are needed. |
Where smart pointers sit relative to containers and resource handles
Choosing a Tool Step by Step
The decision is a sequence of questions, asked in order. The first question is whether a container or resource handle fits the need. If it does, that is the answer. The second question is whether the code truly needs pointer semantics. If it does not, the source prefers a resource handle with operations designed for that specific resource. Only when pointer semantics are truly needed does the choice narrow to a smart pointer, and then it depends on how many owners the object has and whether it is polymorphic.
Read the flowchart as a ranking. A container or resource handle sits at the top, so it is checked first. Smart pointers appear only after pointer semantics have been confirmed as necessary. Notice that the flowchart does not begin with the question 'does this resource need cleanup?' Almost every resource needs cleanup, so that question cannot separate the options. The real discriminator is whether pointer semantics are needed.
Sharing Requires Pointer Semantics
The first situation that calls for pointer semantics is sharing. When an object is shared, several parts of a program need a way to refer to the same object, and that requires pointers or references. In that case shared_ptr becomes the obvious choice. There is one important exception. If the object has an obvious single owner, shared_ptr is not needed, and a simpler tool suits the design better.
Notice what shared_ptr does not do. The source states that shared_ptr does not, by itself, provide rules about which of its owners may read or write the shared object. The pointer solves the question of who refers to the object and when it is released. It leaves the question of who may change it entirely open. A design that shares an object must answer that second question separately.
Single Owner Versus Shared Owners
The phrase single owner matters. The source pairs unique_ptr with a single owner of a polymorphic object, and it pairs shared_ptr with sharing. These are two different patterns. The difference between them is the number of owners, not the presence of polymorphism alone. The diagram below shows the two patterns side by side. In the single-owner pattern, exactly one owner refers to the object. In the shared pattern, several owners refer to the same object, and shared_ptr is the tool that expresses that.
When you read the diagram, count the owners before you look at anything else. One owner points to unique_ptr. Several owners point to shared_ptr. The pointer type follows from that count, and the presence of polymorphism does not change the count.
Polymorphic Objects Need a Pointer
The second situation that calls for a pointer is a reference to a polymorphic object. The reason is that the code using the object does not know its exact type, and it may not know its size either. Because of that uncertainty, the code must hold a pointer or reference rather than the object itself. When a single owner holds such an object, unique_ptr becomes the obvious choice.
Sharing and polymorphism can occur together. The source states that a shared polymorphic object typically requires shared_ptr. The reasoning follows from the two rules already described. The object is polymorphic, so the code refers to it through a pointer or reference. The object is shared, so several owners must refer to it. Choosing unique_ptr in that case would conflict with the idea of multiple owners, which is why shared_ptr is the usual answer.
Polymorphism tells you that the code needs a pointer or reference. The number of owners tells you which smart pointer expresses that need. Keep these two questions separate.
Tracing Owners Over Time
Ownership of a shared object is not fixed. It changes as owners are added and as they go away. The trace below follows one shared object through a short sequence of events. Each step shows how many owners remain, and the final step shows when the object is released. The count is a simple way to describe the situation. It is not a claim about any particular implementation.
Read the trace from left to right. The object stays alive for as long as at least one owner still refers to it. It is released only at the step where no owners remain. This is exactly the question shared_ptr answers: who refers to the object, and when is it released? Notice that nothing in the trace says who may modify the object. That question stays open, as the next sections explain.
Worked Example: Picking a Pointer
Three design situations
For each situation, decide whether a container or resource handle is the better choice, and if a smart pointer is needed, choose unique_ptr or shared_ptr. Situation one: a function must hand back a group of parsed records to its caller. Situation two: a single owner holds a polymorphic shape object whose exact type is not known to the code that draws it. Situation three: a cache object is referred to by several parts of a program at once.
- 1Check for a container first
Situation one is a group of objects returned from a function. The source says a function does not need a pointer to return a collection, because a container that is a resource handle can carry the collection back simply and efficiently. The answer is a container, and no smart pointer is involved.
- 2Confirm that pointer semantics are needed
Situation two involves a polymorphic object. The code does not know its exact type, and it may not know its size, so the code must hold a pointer or reference. Pointer semantics are needed here.
- 3Count the owners
In situation two, a single owner holds the object. The source pairs a single owner of a polymorphic object with
unique_ptr, sounique_ptris the answer. - 4Check for sharing
Situation three has several parts of the program referring to the same object. That is sharing, so pointer semantics are needed, and
shared_ptris the usual answer. Because the object has several owners,unique_ptrwould conflict with the design.
Situation one uses a container. Situation two uses unique_ptr. Situation three uses shared_ptr. Only two of the three situations needed a smart pointer at all, and that outcome reflects the ranking the source describes.
Sharing Is Not Synchronization
Choosing the right pointer solves ownership and release problems. It does not solve every problem that shared data can create. The source is explicit that data races and other forms of confusion are not addressed simply by eliminating resource-management issues. A program can use shared_ptr correctly from the standpoint of lifetime, so the object is never released too early and never leaked, and still have confused or conflicting access to the object it shares.
Consider what this means in a design. If several parts of a program share an object through shared_ptr, the lifetime is handled. The question of which part may change the object, and how changes are coordinated, must still be answered by the program itself. Skipping that question because the pointer type looked correct is a common way to create confusing behavior.
Common Mistakes
Common mistake Wrapping every resource in a smart pointer by default
What it looks likeA function that returns a group of objects is written to return a smart pointer to a collection, even though a container can carry the collection back directly.
Why it failsSmart pointers are a second choice in the resource-management hierarchy. Containers and resource handles operate at a higher conceptual level and fit many needs without any pointer.
Do this insteadCheck whether a container or resource handle fits first. Reach for a smart pointer only when pointer semantics are needed.
Common mistake Using shared_ptr when the object has one obvious owner
What it looks likeAn object that only one part of the program ever holds is wrapped in shared_ptr out of habit.
Why it failsThe source says
shared_ptris not needed when the object has an obvious single owner, and a simpler tool suits the design better.Do this insteadCount the owners. Use the simpler tool when there is one obvious owner.
Common mistake Choosing unique_ptr for a polymorphic object that is shared
What it looks likeA polymorphic object that several parts of the program refer to is held through unique_ptr.
Why it failsunique_ptrexpresses a single owner. A shared polymorphic object typically requiresshared_ptr, andunique_ptrconflicts with the idea of multiple owners.Do this insteadDecide the number of owners first. Use shared_ptr when several owners refer to the object.
Common mistake Assuming smart pointers make shared data safe to access
What it looks likeTwo parts of a program share an object through shared_ptr, and both modify it freely, assuming the pointer type has taken care of the conflict.
Why it failsshared_ptrdoes not decide who may read or write a shared object, and it does not prevent data races.Do this insteadAnswer the access question separately in the design, because lifetime handling does not answer it.
Common mistake Treating the presence of cleanup as the deciding question instead of whether pointer semantics are needed
Practice Scenarios
A program builds a list of configuration entries inside a function and returns that list to the caller. Should the design use a smart pointer to return the list? Explain your reasoning using the ranking of containers and smart pointers.
Hints
- Ask first whether a container or resource handle fits the need.
- The source says a function does not need a pointer to return a collection when a container is used.
A game engine holds a single Enemy object through a base-class pointer. The base class has several derived types, and the engine calls functions on the object without knowing which derived type it is. Only the engine's main loop owns the object. Which smart pointer fits, and why?
Hints
- Is the object polymorphic, and does the code know its exact type?
- Count the owners. How many owners does the object have?
Two subsystems of a program both refer to the same polymorphic logger object. Each subsystem may outlive the other. The team chooses shared_ptr to manage the logger's lifetime, and then assumes both subsystems can safely write to the logger at the same time. What part of this design is still unanswered, and why?
Hints
- What question does
shared_ptranswer, and what question does it leave open? - Separate lifetime from access control.
Key Takeaways
- Smart pointers are a second choice for resource management. Containers and resource handles, which operate at a higher conceptual level, are checked first.
- The deciding question is whether the code truly needs pointer semantics, not whether a resource needs cleanup.
- Use
shared_ptrwhen several owners refer to an object, andunique_ptrwhen a single owner refers to a polymorphic object. A shared polymorphic object typically requiresshared_ptr. - A returned collection does not need a pointer when a container is used, and smart pointers resolve ownership and release without deciding who may read or write a shared object or preventing data races.