Unique Pointers
A smart pointer automatically manages the memory of the object it points to.
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.
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.
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.
- 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.
Checking the Trace in Three Steps
- 1Identify the type named in the template argument. In
make_unique<S>, that type is S, so S is the type of object being created. - 2Match 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.
- 3Confirm 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?
Reveal 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.
make_shared() is provided by the standard library in the header memory, which is the header the example program needs for its creation calls.
- 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
| Aspect | make_unique() | make_shared() |
|---|---|---|
| Creates the object | Yes, in one expression | Yes, in one expression |
| Returns a smart pointer | Yes | Yes |
| Kind of smart pointer returned | Unique pointer | Shared pointer |
| Header | Standard library creation function | Provided by the memory header |
| Used as | auto 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 likeauto p2 = make_unique<S>(3, "Atlantis", 11.3); then assuming p2 is an SWhy it failsThe result of
make_uniqueis a smart pointer that points to the new object, not the object.Do this insteadRead 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 likeS* raw = new S(3, "Atlantis", 11.3); followed by a separate handoff to a smart pointer
Why it failsThis is the verbose pattern the source describes, where creation and ownership transfer are split across statements.
Do this insteadUse 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 likeChoosing make_shared when the design needs a unique pointer
Why it failsThe source ties
make_uniqueto unique pointers andmake_sharedto shared pointers.Do this insteadDecide which kind of pointer the design needs first, then choose the matching function.
Common mistake Forgetting the header for make_shared
What it looks likeCalling make_shared without including the memory header
Why it failsThe source states
make_sharedis provided by the standard library in the memory header.Do this insteadInclude the memory header in any program that uses the creation calls.
Practice: Predict and Rewrite
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.
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()andmake_shared()create the object and return a smart pointer in one expression, withmake_uniquegiving a unique pointer andmake_sharedgiving 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.