Smart Pointers and Ownership in C++
A smart pointer is a small object that refers to another object and takes part in deciding when that object is no longer needed.
The Question Every Heap Object Raises
Every heap object in a C++ program needs someone responsible for it. A smart pointer is a small object that refers to another object and takes part in deciding when that object is no longer needed. The C++ standard library offers two smart pointers that cover most of these cases: unique_ptr and shared_ptr.
The two look similar on the surface, since both point at something. They answer different questions, though. The first question is who owns this object. The second is whether more than one owner can exist at the same time. Most of the decision between the two comes down to the answer to those two questions.
What do you think happens?
If one pointer hands its object to another pointer, how many pointers should own that object once the handover finishes?
Reveal answer
One, because a unique_ptr expresses exactly one owner at a time
The source describes unique_ptr as expressing exactly one owner at a time. The sections that follow show how that single owner is passed along.
Tracing a Single Owner with unique_ptr
unique_ptr is the source's choice for objects of polymorphic type, and it expresses exactly one owner at a time. Think of a unique_ptr as a label on the object that says there is one responsible party. When that party is replaced, the label moves with the object and the old holder no longer has any claim on it.
The source also states that the preference is for unique_ptr when the object has one clear owner. The type itself says so directly, so a reader of the code can see at a glance that only one pointer controls the object.
1
42make_uniquecreates the object and gives a the only ownership of it- std::move transfers that ownership to b
- After the transfer, a holds nothing, so comparing it to nullptr prints 1
- b now owns the object, so dereferencing it prints 42
Read the diagram as a handover. Before the move, a is the one owner and b refers to nothing. After std::move, the object has not been copied and it has not been destroyed. Its single owner has simply changed from a to b, and a is left empty. The count of owners is still exactly one at every moment, which is what the source means by a unique_ptr expressing exactly one owner.
Counting Owners with shared_ptr
shared_ptr is the source's choice for shared objects, where several owners keep the object alive until the last one is released. The source asks you to read the states as a count of owners. Start with one shared_ptr that owns an object. When a second shared_ptr is made to refer to the same object, there are now two owners, and neither one is the primary owner.
Each owner can be released independently. The object stays alive while at least one owner remains. Only when the last owner is released does the object go away. Releasing one owner does not affect the others, which is the relationship the source describes as sharing.
1
2
1- p starts as the only owner, so the count is 1
- q is a second owner inside the inner block, so the count rises to 2
- When q goes out of scope it releases its ownership, and the count falls back to 1
- The object is still alive at the end because p has not been released
The transitions in the diagram follow the program's scopes. Creating a copy adds an owner, and letting a copy go out of scope removes one. The object is removed only at the terminal state, when no owner remains. The lifetime of the object is therefore tied to all of its owners together, not to any single one.
Polymorphic Objects Behind a Base-Class Pointer
The source names unique_ptr as its choice for objects of polymorphic type. A polymorphic type is one whose objects can be used through a common base-class interface while the actual object is a derived type. In the example below, the base class declares a virtual function, which is what makes the type polymorphic.
- Shape is the base type and declares the virtual area function
- Circle is the derived type that provides the actual implementation
- The variable s has the base-class type, but the object it owns is a Circle
- Ownership is still exactly one owner, the
unique_ptrnamed s
The diagram separates two ideas that are easy to blur. The pointer's declared type is the base type, which is what the code can use. The object owned at runtime is the derived type. The unique_ptr still has one owner, so the single-owner rule applies no matter how many types sit between the pointer and the object's actual class.
Side-by-Side: Single Owner Versus Shared Owners
| Question | unique_ptr | shared_ptr |
|---|---|---|
| Who owns the object? | Exactly one owner at a time | Several owners can exist at the same time |
| When is the object released? | When its single owner releases it | When the last owner is released |
| Preferred when | The object has one clear owner | The object really is shared |
| Source's default | Preferred over shared_ptr | Used when sharing is genuinely needed |
| What the type says | Only one pointer controls the object | Several pointers keep the object alive together |
Ownership rules for the two smart pointers named in the source
The table makes the central contrast explicit. The unique_ptr answers the question with a single name, and the shared_ptr answers it with a count that can grow and shrink. Neither is more correct in general. The source's preference simply tells you where to start.
Choosing Between the Available Tools
The source gives a clear preference order. It says to prefer unique_ptr to shared_ptr. This is a default, not a ban on shared_ptr. Use shared_ptr when the object really is shared, and use unique_ptr when the object has one clear owner.
The source also prefers smart pointers to garbage collection. Garbage collection is a system in which the runtime finds and reclaims unused memory on its own, without tying each release to a specific owner in the code. Smart pointers keep the release tied to ownership, so a reader can tell which pointer controls which object. The source's preference points toward making ownership explicit in the code.
The source further advises preferring resource handles with specific semantics to smart pointers. A resource handle is a type that manages a particular resource and whose behavior is defined for that resource. Smart pointers are a general tool for ownership of objects. When a more specific handle exists, using it expresses the intent more precisely. The decision flow below follows that order from the most specific tool to the most general one.
Garbage collection sits outside this flow, because the source prefers smart pointers to it. In other words, if a smart pointer expresses the ownership, the source's guidance is to use that rather than leave release to a separate runtime mechanism.
Mistakes When Choosing a Pointer
Common mistake Reaching for shared_ptr everywhere because it seems more flexible
What it looks likeEvery object in a program is wrapped in shared_ptr even though each one has only one clear owner.
Why it failsShared ownership adds a second owner to reason about, and the object's lifetime then depends on all of those owners together.
Do this insteadStart with unique_ptr and add sharing only when the object really is shared.
Common mistake Treating a moved-from unique_ptr as if it still owns the object
What it looks likeAfter b is assigned std::move(a), the code keeps using a as though it still refers to the object.
Why it failsThe ownership has moved to b, and the source's single-owner model means a no longer controls the object.
Do this insteadAfter a move, use the pointer that now owns the object, and check for null where the old pointer might be empty.
Common mistake Assuming a shared object is destroyed when any one owner is released
What it looks likeOne of two shared_ptr owners goes out of scope and the programmer expects the object to be deleted immediately.
Why it failsThe object stays alive while at least one owner remains, and only the last release ends its life.
Do this insteadCount the remaining owners; the object goes away only when that count reaches zero.
Common mistake Choosing a general smart pointer when a specific resource handle already exists
What it looks likeA resource with a dedicated handle type is wrapped in a generic pointer out of habit.
Why it failsThe source says a resource handle with specific semantics is preferable to a general smart pointer because it expresses intent more precisely.
Do this insteadCheck whether a more specific handle fits before falling back to unique_ptr or shared_ptr.
Trace and Choose
A program creates an object with one clear owner, then makes three shared_ptr copies of a different object that is genuinely used by several parts of the program. For each object, decide which pointer type the source's preference order points to, and explain what happens to the object's owner count as the copies are released.
Hints
- Start with the question of whether the object has one clear owner or is genuinely shared.
- The source's default is
unique_ptr;shared_ptris used when the object really is shared. - For the shared object, the lifetime ends only when the last owner is released.
Wrapping Up Ownership Choices
- 1
unique_ptrexpresses exactly one owner at a time, and it is the source's choice for objects of polymorphic type. - 2
shared_ptrkeeps a shared object alive until the last of its several owners is released. - 3The source prefers
unique_ptrtoshared_ptr, so start with a single owner and add sharing only when it is genuinely needed. - 4The source prefers resource handles with specific semantics to smart pointers, and smart pointers to garbage collection, so choose the most specific tool that expresses the ownership.
Key Takeaways
unique_ptrexpresses exactly one owner at a time and is the source's choice for polymorphic objects.shared_ptrlets several owners keep an object alive, and the object is released only when the last owner goes away.- The source's default is
unique_ptr;shared_ptris a choice made when the object really is shared, not a general-purpose default. - The source prefers resource handles with specific semantics to smart pointers, and smart pointers to garbage collection.