Concepts / Unique Pointers

Unique Pointers

A smart pointer automatically manages the memory of the object it points to.

  • Programming

Why a Smart Pointer Needs an Object First

A smart pointer is a pointer that automatically manages the memory of the object it points to. That job sounds simple, but a smart pointer cannot manage anything until an object exists. In the usual arrangement, the object lives on the free store, and a pointer to it has to be handed to the smart pointer. This article looks at how that two-step arrangement works, why the source describes it as awkward, and how the standard library's make functions collapse it into one expression.

As you read, keep one question in mind: which part of the program is responsible for creating the object, and which part is responsible for managing it? The answer to that question is the key to every example in this article.

The Verbose Two-Step Pattern

The object is created in one place and its ownership is then transferred in another. First, the program creates an object on the free store. Second, it passes the pointer to that object to a smart pointer, which takes over managing the memory. The source describes this process as logically a bit odd and verbose, because the creation and the handoff are separated into different statements.

Before
S* raw = new S(3, "Atlantis", 11.3);
std::unique_ptr<S> p1(raw);

Two separate steps: allocate the object on the free store, then hand its pointer to a smart pointer.

After
auto p2 = make_unique<S>(3, "Atlantis", 11.3);

One expression: the object is created and the smart pointer is returned together.

The first version is the verbose pattern the source warns about. It is correct, but the object is created in one line and given to a smart pointer in another. The second version is what the standard library offers as a compensation: a single call that both creates the object and returns a smart pointer that already holds it.

Tracing the Line That Creates p2

The example program in the source contains one line that matters most: the auto declaration of p2. Read it as a request. It names the type S, passes the three arguments 3, "Atlantis", and 11.3, and calls the function make_unique. Notice that there is no separate step that allocates an S on the free store and then wraps it. That single expression covers both jobs.

cpp
Notes on the code
  • auto: the declared type is inferred by the compiler from the return value of make_unique()
  • make_unique: the function that creates the object and the smart pointer together
  • <S>: the template argument naming the type of object to create
  • 3, "Atlantis", 11.3: the values that must match a constructor of S

Because auto is used, the declaration does not spell out the pointer type. The compiler infers it from what make_unique() returns. The result is a smart pointer, and that smart pointer is what p2 holds.

make_unique<S>(...)creates the objectObject of type Sthe object on the freestoreSmart pointer p2holds and manages theobjectcreatesreturnsmanages memory of
The learner question: which part creates the object, and which part manages its memory? make_unique creates the object, and the smart pointer p2 manages the memory the object occupies.

Checking the Trace in Three Steps

  1. 1
    Identify the type named in the template argument. In make_unique<S>, that type is S, so S is the type of object being created.
  2. 2
    Match each argument to a constructor parameter. The values 3, "Atlantis", and 11.3 must fit a constructor of S. If they do not, the expression will not compile as intended.
  3. 3
    Confirm what the declared variable holds. p2 holds a smart pointer that points to the new object. It does not hold the object itself.

If the result differs from what you expected, the mismatch usually sits in one of these three places: the type in the template argument, the argument list, or the assumption about what the variable holds.

What do you think happens?

After the declaration auto p2 = make_unique<S>(3, "Atlantis", 11.3); runs, what does p2 hold?

  • A smart pointer that points to a new S object

  • The S object itself, with no pointer involved

Reveal answer
Answer:

A smart pointer that points to a new S object

The source states that the result of the call is a smart pointer, and that smart pointer is what p2 holds. The object itself is not stored directly in p2.

Make Functions for Each Smart Pointer Kind

The verbosity problem is not unique to one kind of smart pointer. The standard library therefore provides two creation functions. make_unique() creates a unique pointer, and make_shared() creates a shared pointer. Each creates the object and returns a smart pointer to it in one expression.

Key point

make_shared() is provided by the standard library in the header memory, which is the header the example program needs for its creation calls.

cpp
Notes on the code
  • u: created by make_unique, so it is a unique pointer
  • s: created by make_shared, so it is a shared pointer
  • Both lines use the same arguments and the same template argument; only the kind of pointer returned differs

From the programmer's side, the two functions are used the same way. You name the type, pass the constructor arguments, and receive a smart pointer. The difference lies in which kind of smart pointer you get back, so choosing between them is a decision about the kind of pointer the design needs.

Comparing What Each Function Returns

Aspectmake_unique()make_shared()
Creates the objectYes, in one expressionYes, in one expression
Returns a smart pointerYesYes
Kind of smart pointer returnedUnique pointerShared pointer
HeaderStandard library creation functionProvided by the memory header
Used asauto p = make_unique<T>(args...);auto s = make_shared<T>(args...);

Both functions create and return a smart pointer in one step; the returned pointer type is the main difference the source identifies.

The source identifies make_unique() with unique pointers and make_shared() with shared pointers. When you read a declaration, the function name tells you which kind of smart pointer the variable holds.

Mistakes Learners Make with Creation Calls

  • Common mistake

    Treating the variable as the object itself

    What it looks like
    auto p2 = make_unique<S>(3, "Atlantis", 11.3); then assuming p2 is an S
    Why it fails

    The result of make_unique is a smart pointer that points to the new object, not the object.

    Do this instead

    Read p2 as a smart pointer and access the object through it, as the smart pointer's interface provides.

  • Common mistake

    Still writing the two-step pattern when a single call exists

    What it looks like

    S* raw = new S(3, "Atlantis", 11.3); followed by a separate handoff to a smart pointer

    Why it fails

    This is the verbose pattern the source describes, where creation and ownership transfer are split across statements.

    Do this instead

    Use make_unique or make_shared to create the object and the smart pointer in one expression.

  • Common mistake

    Mixing up which function gives which pointer kind

    What it looks like

    Choosing make_shared when the design needs a unique pointer

    Why it fails

    The source ties make_unique to unique pointers and make_shared to shared pointers.

    Do this instead

    Decide which kind of pointer the design needs first, then choose the matching function.

  • Common mistake

    Forgetting the header for make_shared

    What it looks like

    Calling make_shared without including the memory header

    Why it fails

    The source states make_shared is provided by the standard library in the memory header.

    Do this instead

    Include the memory header in any program that uses the creation calls.

Practice: Predict and Rewrite

EASY

Rewrite the following two-step code as a single declaration using make_unique. The two-step version is: S* raw = new S(3, "Atlantis", 11.3); std::unique_ptr<S> p1(raw); Then state which pointer kind your rewritten declaration produces.

Hints
  • Find the type that appears in the new expression; that becomes the template argument.
  • The arguments in the new expression become the arguments after the function name.
  • Make_unique produces a unique pointer, so the rewritten declaration holds a unique pointer.
MEDIUM

Write one declaration that creates an S from the same three arguments and holds a shared pointer to it. Then name the header the program needs.

Hints
  • The creation function for shared pointers is make_shared.
  • The source names the standard library header memory for make_shared.

Key Takeaways

  • A smart pointer manages the memory of the object it points to, but an object must exist before a smart pointer can manage it.
  • The verbose pattern creates an object on the free store and then hands its pointer to a smart pointer in a separate step.
  • make_unique() and make_shared() create the object and return a smart pointer in one expression, with make_unique giving a unique pointer and make_shared giving a shared pointer.
  • When tracing a creation line, check the template argument's type, match the arguments to a constructor, and confirm the variable holds a smart pointer rather than the object.

Related concepts

More concepts starting with U