Concepts / Smart Pointers and Ownership in C++

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.

  • Programming

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?

  • Zero, because the object has to be copied

  • One, because a unique_ptr expresses exactly one owner at a time

  • Two, because both pointers now refer to it

Reveal answer
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.

cpp
Output
1
42
Notes on the code
  • make_unique creates 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
aowns the objectobjectone owneranullobjectone ownerbnothing yetbowns the objectownsno longer ownsowns after move
After std::move, which pointer owns the object, and which one is null?

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.

cpp
Output
1
2
1
Notes on the code
  • 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
one ownercount 1two ownerscount 2one ownercount 1object deletedcount 0second copy createda copy is releasedlast owner released
How does the number of owners change as shared_ptr copies go in and out of scope, and when is the object deleted?

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.

cpp
Notes on the code
  • 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_ptr named s
unique_ptr<Shape>the single ownerCircle objectthe owned objectShapebase typeownsderived from
How does a base-class unique_ptr relate to the derived object it owns?

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

Questionunique_ptrshared_ptr
Who owns the object?Exactly one owner at a timeSeveral owners can exist at the same time
When is the object released?When its single owner releases itWhen the last owner is released
Preferred whenThe object has one clear ownerThe object really is shared
Source's defaultPreferred over shared_ptrUsed when sharing is genuinely needed
What the type saysOnly one pointer controls the objectSeveral 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.

Does a resourcehandle withspecific semanticsfit?Use that resourcehandlemost specific toolUse unique_ptrone clear ownerIs the object sharedby several owners?Use shared_ptrgenuinely sharedyesnonoyes
Given a resource and its ownership situation, which option does the source's decision order lead to?

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 like

    Every object in a program is wrapped in shared_ptr even though each one has only one clear owner.

    Why it fails

    Shared ownership adds a second owner to reason about, and the object's lifetime then depends on all of those owners together.

    Do this instead

    Start 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 like

    After b is assigned std::move(a), the code keeps using a as though it still refers to the object.

    Why it fails

    The ownership has moved to b, and the source's single-owner model means a no longer controls the object.

    Do this instead

    After 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 like

    One of two shared_ptr owners goes out of scope and the programmer expects the object to be deleted immediately.

    Why it fails

    The object stays alive while at least one owner remains, and only the last release ends its life.

    Do this instead

    Count 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 like

    A resource with a dedicated handle type is wrapped in a generic pointer out of habit.

    Why it fails

    The source says a resource handle with specific semantics is preferable to a general smart pointer because it expresses intent more precisely.

    Do this instead

    Check whether a more specific handle fits before falling back to unique_ptr or shared_ptr.

Trace and Choose

MEDIUM

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_ptr is 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. 1
    unique_ptr expresses exactly one owner at a time, and it is the source's choice for objects of polymorphic type.
  2. 2
    shared_ptr keeps a shared object alive until the last of its several owners is released.
  3. 3
    The source prefers unique_ptr to shared_ptr, so start with a single owner and add sharing only when it is genuinely needed.
  4. 4
    The 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_ptr expresses exactly one owner at a time and is the source's choice for polymorphic objects.
  • shared_ptr lets 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_ptr is 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.

Related concepts

More concepts starting with S