118

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Eternal
  • 2,648
  • 2
  • 15
  • 21
  • 4
    *However, that seems like a pretty weak reason* --> Why it's a weak reason? It effectively reduces code duplication of type. As for the deleter, how often you are using a custom deleter when you use `std::unique_ptr`? It's not a argument to against `make_unique` – llllllllll Dec 20 '18 at 14:34
  • 3
    I say it's a weak reason because if there was no `std::make_unique` in the first place, I don't think that would be reason enough to add it to the STL, especially when it's a syntax which is less expressive than using the constructor, not more – Eternal Dec 20 '18 at 15:26
  • 1
    If you have a program, created in c++14, using make_unique, you do not want the function to get removed from stl. Or if you want it to be backwards compatible. – Serge Dec 20 '18 at 15:31
  • 2
    @Serge That's a good point, but it's a bit besides the object of my question. I'll make an edit to make it clearer – Eternal Dec 20 '18 at 15:44
  • 3
    @Eternal please stop refering to C++ Standard Library as STL as it is incorrect and creates confusion. See https://stackoverflow.com/questions/5205491/whats-the-difference-between-stl-and-c-standard-library – Marandil Dec 21 '18 at 10:37
  • I wasn't aware of that. Thanks – Eternal Dec 21 '18 at 11:00
  • I just wish we could write `std::unique_ptr::make(param)` instead of needing a `make_XXXX` freestanding function because of language restrictions. – einpoklum Jan 01 '19 at 09:43
  • @einpoklum Having a `std::unique_ptr::make(param)` would actually mean less encapsulation, because the function would have access to the private members of the class. It's not like the `make_XXXX` functions are global functions either: they're in the namespace `std` and are included from headers that make sense. See this for more info on members vs freestanding functions: http://gotw.ca/publications/mill02.htm – Eternal Jan 02 '19 at 11:17
  • @Eternal: 1. I didn't argue for it because of encapsulation. 2. Since the class author would write it, I don't see why such access would be a problem. 3. Indeed, I should have said `std::make_XXXX` rather than `make_XXXX`, I didn't mean to suggest it's a global function. – einpoklum Jan 02 '19 at 12:03
  • @einpoklum I didn't mean you argued about encapsulation or global function. Sorry I didn't express myself clearly. I was basically trying to say that having a freestanding function in this context is considered a better practice than having a member function. See http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197 for more details – Eternal Jan 02 '19 at 13:00
  • Well, C++17 has not actually *clarified* the evaluation order (there was nothing *unclear* about it before), though it has *constrained* it (not all that was legal is still allowed). – Deduplicator Apr 20 '19 at 19:56

6 Answers6

91

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 53
    @reggaeguitar If I see a `new` I need to stop and think: how long is this pointer going to live? Did I handle it correctly? If there is an exception, is everything cleaned up correctly? I'd like to not ask myself those questions and waste my time on it and if I don't use `new`, I don't have to ask those questions. – NathanOliver Dec 20 '18 at 23:39
  • 8
    Imagine you do a grep over all the source files of your project and don't find a single `new`. Wouldn't this be wonderful? – Sebastian Mach Dec 21 '18 at 09:26
  • 5
    The main advantage of the "don't use new" guideline it that it's simple, so it's an easy guideline to give to the less experienced developers you may be working with. I hadn't realized at first, but that has value in and of itself – Eternal Dec 22 '18 at 12:43
  • 2
    @NathanOliver You actually don't _absolutely_ have to be using `std::make_shared` - imagine a case where the allocated object is big and there are a lot of `std::weak_ptr`-s pointing to it: it would be better to let the object be deleted as soon as the last shared pointer is destroyed and live with just a small shared area. – Dev Null Apr 11 '19 at 06:19
  • @DevNull How would you get into that case without creating a `weak_ptr` from a `shared_ptr` in the first place? – NathanOliver Apr 11 '19 at 12:28
  • 1
    @NathanOliver you wouldn't. What I'm talking about is the disadvantage of `std::make_shared` https://stackoverflow.com/a/20895705/8414561 where the memory that was used to store the object can't be freed until the last `std::weak_ptr` is gone (even if all `std::shared_ptr`-s pointing to it (and consequently the object itself) have been already destroyed). – Dev Null Apr 11 '19 at 23:41
62

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[]>.

Caleth
  • 52,200
  • 2
  • 44
  • 75
21

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.

Pharap
  • 3,826
  • 5
  • 37
  • 51
273K
  • 29,503
  • 10
  • 41
  • 64
  • 2
    Well, as I said in the question, I can see it's less typing with only one mention of `MyClass`, but I was wondering if there was a stronger reason to use it – Eternal Dec 20 '18 at 15:37
  • 2
    In many cases deduction guide would help to eliminate the `` part in the first variant. – AnT stands with Russia Dec 20 '18 at 15:52
  • 11
    It's already been said in the comments for other answers, but while c++17 introduced template type deduction for constructors, in the case of `std::unique_ptr` it's disallowed. It has to do with distinguishing `std::unique_ptr` and `std::unique_ptr` – Eternal Dec 20 '18 at 17:53
19

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

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.)

einpoklum
  • 118,144
  • 57
  • 340
  • 684
-2

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.

  • 4
    The author explicitly specified in question that in C++17 leaks shall not occur anymore. "Since then, C++17 has clarified the evaluation order, making Syntax A safe too, so here's my question: (...)". You did not answer his question. – R2RT Dec 03 '19 at 06:55