Lesson 5 of 6
std::make_unique<T>(args...) and std::make_shared<T>(args...) construct an object and wrap it
in one step.
#include <iostream>
#include <memory>
#include <string>
struct Widget {
std::string name;
int size;
Widget(std::string n, int s) : name(std::move(n)), size(s) {}
};
int main() {
// The arguments are forwarded to Widget's constructor.
auto a = std::make_unique<Widget>("alpha", 10);
auto b = std::make_shared<Widget>("beta", 20);
std::cout << a->name << " " << a->size << "\n";
std::cout << b->name << " " << b->size << "\n";
return 0;
}
alpha 10
beta 20
Both work with the new form too — std::unique_ptr<Widget>(new Widget("alpha", 10)). The
factories are preferred for three reasons, and only the first is cosmetic.
Reason 1: the type is written once
auto a = std::unique_ptr<Widget>(new Widget("alpha", 10)); // Widget twice
auto b = std::make_unique<Widget>("alpha", 10); // Widget once
Change the type and there is one place to edit rather than two, which cannot get out of sync.
Reason 2: make_shared allocates once
std::shared_ptr<T>(new T) performs two allocations: one for the object, one for the control
block. make_shared performs one, placing both in a single block.
#include <iostream>
#include <memory>
#include <cstdlib>
#include <new>
int allocations = 0;
void* operator new(std::size_t size) {
++allocations;
void* p = std::malloc(size);
if (!p) {
throw std::bad_alloc();
}
return p;
}
void operator delete(void* p) noexcept { std::free(p); }
void operator delete(void* p, std::size_t) noexcept { std::free(p); }
int main() {
allocations = 0;
{
std::shared_ptr<int> a(new int(1));
std::cout << "shared_ptr(new int): " << allocations << " allocation(s)\n";
}
allocations = 0;
{
auto b = std::make_shared<int>(1);
std::cout << "make_shared<int>: " << allocations << " allocation(s)\n";
}
return 0;
}
shared_ptr(new int): 2 allocation(s)
make_shared<int>: 1 allocation(s)
Half the allocations, and the object sits next to its counter in memory, so touching one brings the other into cache.
The trade-off: because both live in one block, the memory cannot be released until the last
weak_ptr is also gone. An object with a large body and long-lived weak_ptrs watching it will
hold its bytes longer than necessary. That is a genuine but narrow reason to prefer the new
form.
Reason 3: exception safety
This is the one that actually matters, and it is not obvious.
void process(std::unique_ptr<Widget> w, int value);
int risky();
// Potentially leaks:
process(std::unique_ptr<Widget>(new Widget("a", 1)), risky());
The compiler may evaluate the arguments in any order (lesson 8.7 called this unspecified). One legal order is:
new Widget("a", 1)— allocatesrisky()— throwsstd::unique_ptr<Widget>(...)— never runs
The Widget was allocated and never wrapped, so nothing will ever free it. A leak from code that
looks careful.
// Cannot leak:
process(std::make_unique<Widget>("a", 1), risky());
make_unique allocates and wraps inside a single function call, so there is no window between
the two. Whatever order the arguments are evaluated in, the pointer is owned the moment it exists.
C++17 tightened the evaluation rules so each argument is now fully evaluated before the next begins, which closes this particular hole. The habit is still right: the factory form has no window at all, and does not depend on your readers knowing which standard you compile with.
When you cannot use them
A custom deleter. make_unique and make_shared always use delete:
std::unique_ptr<Handle, decltype(&close_handle)> h(open_handle(7), &close_handle);
A private constructor. The factories call the constructor from inside the standard library, which is not a friend of your class.
new T[n]. std::make_unique<T[]>(n) exists and is fine; make_shared for arrays only
arrived in C++20.
You need new T() versus new T. make_unique<T>() value-initialises; there are corner
cases where you specifically want the uninitialised form for performance.
Outside those, use the factories.
make_unique was late
A historical footnote worth knowing when reading older code: make_shared arrived in C++11 but
make_unique was forgotten and only added in C++14. Code written in between uses
std::unique_ptr<T>(new T(...)) — not because the author was careless, but because the factory
did not exist.
Try it
Write a class whose constructor takes three arguments and print when it is constructed and
destroyed. Create it with make_unique, with make_shared, and with the new forms. Then use
the allocation counter to compare shared_ptr(new T) against make_shared<T>().
Checkpoint
make_unique<T>(args...)andmake_shared<T>(args...)construct and wrap in one step.- The type is named once, so it cannot drift out of sync.
make_sharedperforms one allocation instead of two, keeping object and counter together.- Its trade-off: the memory is held until the last
weak_ptris gone too. - The factories close the exception-safety window between allocating and wrapping.
- Use
newonly for a custom deleter, a private constructor, or the array corner cases. make_uniquearrived in C++14, which is why older code uses thenewform.
Questions about this lesson
Log in to ask a question.