Concepts / Shared Pointers

Shared Pointers

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

  • Programming

Why Two Steps Feel Awkward

A smart pointer is a pointer that automatically manages the memory of the object it points to. Before a smart pointer can manage anything, though, an object has to exist. That object usually lives on the free store, and a pointer to it has to be handed to the smart pointer. The source describes this two-step process as logically a bit odd and verbose, because the object is created in one place and its ownership is then transferred in another.

The standard library offers a way to compensate for this. Its functions make_unique() and make_shared() create the object and the smart pointer together in a single expression. In this article you will read a short program that uses make_unique(), predict what it builds, and then trace how the pieces fit together. Keep one question in mind as you read: which part of the program is responsible for creating the object, and which part is responsible for managing it?

Predict Who Creates the Object

What do you think happens?

In a single declaration that creates a smart pointer to a new object, which name tells you that the object is being created rather than just managed?

  • The variable name p2

  • The function name make_unique

  • The keyword auto

Reveal answer
Answer:

The function name make_unique

The function is the part of the expression that creates the object. The smart pointer is what the declared variable holds once the function returns it. auto only tells the compiler to infer the declared type.

Tracing a make_unique Line

The line that matters in the example program is the auto declaration of p2. It contains the type S, the three arguments 3, "Atlantis", and 11.3, and the function name make_unique. The program never writes a separate step that allocates an S on the free store and then wraps it. That single expression covers both jobs.

Notes on the code
  • auto: the compiler infers the declared type from the return value of make_unique()
  • make_unique: the function that creates the object and returns a unique pointer to it
  • <S>: the template argument, naming the type of object to create
  • (3, "Atlantis", 11.3): the constructor arguments passed through to the new S

Because auto is used, the declaration does not spell out the pointer type. The compiler infers it from what make_unique() returns. The line is therefore best read as a request: create an S from these three values, and give me a smart pointer that holds it.

Following the Object Through Creation

Tracing the program means following the object from the moment it is requested to the moment a smart pointer holds it. The diagram below shows that path for the p2 line. Each step has a clear owner: the function creates the object, and the smart pointer manages the memory the object occupies.

make_unique<S>request with templateargument SS objectbuilt from 3, Atlantis,11.3Smart pointerreturned by the functionp2holds the smart pointercreateswrapped and returnedassigned to
How the make_unique call for p2 moves from a request, to a new S object, to a smart pointer that p2 holds.

Three Checks When Reading a Creation Line

  1. 1
    Identify the type named in the template argument. That 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 the constructor of S.
  3. 3
    Confirm what the declared variable holds. It is a smart pointer that points to the new object, not the object itself.
  4. 4
    If the result differs from what you expected, the mismatch usually sits in one of these three places.

One-Step Creation Compared

The verbosity problem is about repetition and ownership transfer. With the older pattern, the programmer first creates the object with new and then hands the resulting pointer to a smart pointer. The object is named and created in one place and its management is set up in another. The make functions remove that split. You name the type once, pass the constructor arguments once, and receive a smart pointer in the same expression.

new S(...)create the object on thefree storemake_unique<S>(...)create the object andreturn a smart pointerhand pointer overpass the raw pointer to asmart pointerthen
The two-step pattern creates the object and then hands its pointer to a smart pointer; make_unique() performs both jobs in one expression.

make_shared for the Shared Pointer Case

The same verbosity problem applies to every smart pointer, not only unique ones. The standard library therefore provides make_shared(), which is found in the standard library header <memory>. Like make_unique(), it creates the object and returns a smart pointer to it in one step. The difference is the kind of smart pointer it returns: a shared pointer rather than a unique pointer.

Notes on the code
  • #include <memory>: the header that provides both make functions
  • make_shared<S>: creates an S and returns a shared pointer to it
  • The constructor arguments are used exactly as in the make_unique example

From the programmer's side the two calls look alike. You name the type, pass the constructor arguments, and receive a smart pointer. Choosing between them is a decision about which kind of pointer the design needs. The source identifies make_unique() with unique pointers and make_shared() with shared pointers.

Unique Versus Shared Results

Both functions accept constructor arguments and both return a smart pointer after creating one object. The thing that changes is the pointer type that comes back. The comparison below sets the two creation paths side by side.

make_unique()creation functionmake_shared()creation functionunique pointerreturned smart pointershared pointerreturned smart pointerreturnsreturns
make_unique() and make_shared() take the same kind of arguments but return different kinds of smart pointer.

Mistakes When Reading Creation Lines

  • Common mistake

    Assuming the declared variable holds the object itself

    What it looks like

    auto p2 = std::make_unique<S>(3, "Atlantis", 11.3); then treating p2 as if it were an S

    Why it fails

    The declared variable holds a smart pointer that points to the new object.

    Do this instead

    Read p2 as a smart pointer and access the object through it.

  • Common mistake

    Looking for a separate new step in a make_unique line

    What it looks like

    Searching the p2 line for a new keyword and concluding the object is created elsewhere

    Why it fails

    make_unique() covers both creating the object and wrapping it, in one expression.

    Do this instead

    Treat the make function call as the single place where creation and management are set up.

  • Common mistake

    Forgetting the constructor arguments must match

    What it looks like

    Passing values to make_shared<S>() that do not fit any constructor of S

    Why it fails

    The values 3, "Atlantis", and 11.3 must fit the constructor of S, or the creation fails to compile.

    Do this instead

    Match each argument to a constructor parameter before expecting a result.

  • Common mistake

    Using make_unique() when the design needs a shared pointer, or the reverse, because the two calls look similar

Practice: Trace a Creation Line

EASY

Given the line auto p3 = std::make_shared<T>(7, "Mars"); identify the type being created, name the function that creates it, and state what kind of smart pointer p3 holds. Then say which of the three checks would catch a constructor argument that does not fit T.

Hints
  • The type named in the template argument is T.
  • The function name make_shared tells you the kind of smart pointer returned.
  • The second check, matching arguments to constructor parameters, is the one that catches a mismatched constructor argument.

Key Takeaways

  • A smart pointer automatically manages the memory of the object it points to, but the object must exist first.
  • Creating an object with new and then handing its pointer to a smart pointer is a two-step process the source calls verbose and logically awkward.
  • make_unique() and make_shared(), both in the standard library header <memory>, create the object and the smart pointer together in one expression.
  • make_unique() produces a unique pointer and make_shared() produces a shared pointer, and you choose between them based on the kind of pointer the design needs.
  • When tracing a creation line, identify the template type, match the arguments to constructor parameters, and confirm the variable holds a smart pointer.

Related concepts

More concepts starting with S