Shared Pointers
A smart pointer automatically manages the memory of the object it points to.
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?
Reveal 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.
- 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.
Three Checks When Reading a Creation Line
- 1Identify the type named in the template argument. That is the type of object being created.
- 2Match each argument to a constructor parameter. The values 3, "Atlantis", and 11.3 must fit the constructor of S.
- 3Confirm what the declared variable holds. It is a smart pointer that points to the new object, not the object itself.
- 4If 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.
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.
- #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_uniqueexample
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.
Mistakes When Reading Creation Lines
Common mistake Assuming the declared variable holds the object itself
What it looks likeauto p2 = std::make_unique<S>(3, "Atlantis", 11.3); then treating p2 as if it were an S
Why it failsThe declared variable holds a smart pointer that points to the new object.
Do this insteadRead 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 likeSearching the p2 line for a new keyword and concluding the object is created elsewhere
Why it failsmake_unique()covers both creating the object and wrapping it, in one expression.Do this insteadTreat 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 likePassing values to make_shared<S>() that do not fit any constructor of S
Why it failsThe values 3, "Atlantis", and 11.3 must fit the constructor of S, or the creation fails to compile.
Do this insteadMatch 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
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_sharedtells 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()andmake_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 andmake_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.