Eternal
Eternal

Reputation: 2959

Why use std::make_unique in C++17?

As far as I understand, C++14 introduced std::make_unique because, as a result of the parameter evaluation order not being specified, this was unsafe:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(Explanation: if the evaluation first allocates the memory for the raw pointer, then calls g() and an exception is thrown before the std::unique_ptr construction, then the memory is leaked.)

Calling std::make_unique was a way to constrain the call order, thus making things safe:

f(std::make_unique<MyClass>(param), g());             // Syntax B

Since then, C++17 has clarified the evaluation order, making Syntax A safe too, so here's my question: is there still a reason to use std::make_unique over std::unique_ptr's constructor in C++17? Can you give some examples?

As of now, the only reason I can imagine is that it allows to type MyClass only once (assuming you don't need to rely on polymorphism with std::unique_ptr<Base>(new Derived(param))). However, that seems like a pretty weak reason, especially when std::make_unique doesn't allow to specify a deleter while std::unique_ptr's constructor does.

And just to be clear, I'm not advocating in favor of removing std::make_unique from the Standard Library (keeping it makes sense at least for backward compatibility), but rather wondering if there are still situations in which it is strongly preferred to std::unique_ptr

Upvotes: 131

Views: 40386

Answers (6)

Dhanya Gopinath
Dhanya Gopinath

Reputation: 137

Consider void function(std::unique_ptr(new A()), std::unique_ptr(new B())) { ... }

Suppose that new A() succeeds, but new B() throws an exception: you catch it to resume the normal execution of your program. Unfortunately, the C++ standard does not require that object A gets destroyed and its memory deallocated: memory silently leaks and there's no way to clean it up. By wrapping A and B into std::make_uniques you are sure the leak will not occur:

void function(std::make_unique(), std::make_unique()) { ... } The point here is that std::make_unique and std::make_unique are now temporary objects, and cleanup of temporary objects is correctly specified in the C++ standard: their destructors will be triggered and the memory freed. So if you can, always prefer to allocate objects using std::make_unique and std::make_shared.

Upvotes: -2

einpoklum
einpoklum

Reputation: 132182

Since then, C++17 has clarified the evaluation order, making Syntax A safe too

That's really not good enough. Relying on a recently-introduced technical clause as the guarantee of safety is not a very robust practice:

  • Someone might compile this code in C++14.
  • You would be encouraging the use of raw new's elsewhere, e.g. by copy-pasting your example.
  • As S.M. suggests, since there's code duplication, one type might get changed without the other one being changed.
  • Some kind of automatic IDE refactoring might move that new elsewhere (ok, granted, not much chance of that).

Generally, it's a good idea for your code to be appropriate/robust/clearly valid without resorting to language-laywering, looking up minor or obscure technical clauses in the standard.

(this is essentially the same argument I made here about the order of tuple destruction.)

Upvotes: 2

Caleth
Caleth

Reputation: 63152

make_unique distinguishes T from T[] and T[N], unique_ptr(new ...) does not.

You can easily get undefined behaviour by passing a pointer that was new[]ed to a unique_ptr<T>, or by passing a pointer that was newed to a unique_ptr<T[]>.

Upvotes: 69

3CxEZiVlQ
3CxEZiVlQ

Reputation: 38890

The reason is to have shorter code without duplicates. Compare

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

You save MyClass, new and braces. It costs only one character more in make in comparison with ptr.

Upvotes: 23

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275895

Every use of new has to be extra carefully audited for lifetime correctness; does it get deleted? Only once?

Every use of make_unique doesn't for those extra characteristics; so long as the owning object has "correct" lifetime, it recursively makes the unique pointer have "correct".

Now, it is true that unique_ptr<Foo>(new Foo()) is identical in all ways1 to make_unique<Foo>(); it just requires a simpler "grep your source code for all uses of new to audit them".


1 actually a lie in the general case. Perfect forwarding isn't perfect, {}, default init, arrays are all exceptions.

Upvotes: 21

NathanOliver
NathanOliver

Reputation: 180998

You're right that the main reason was removed. There are still the don't use new guidelines and that it is less typing reasons (don't have to repeat the type or use the word new). Admittedly those aren't strong arguments but I really like not seeing new in my code.

Also don't forget about consistency. You absolutely should be using make_shared so using make_unique is natural and fits the pattern. It's then trivial to change std::make_unique<MyClass>(param) to std::make_shared<MyClass>(param) (or the reverse) where the syntax A requires much more of a rewrite.

Upvotes: 101

Related Questions