20

Just to clarify, using make_unique only adds exception safety when you have multiple allocations in an expression, not just one, correct? For example

void f(T*);

f(new T);

is perfectly exception safe (as far as allocations and stuff), while

void f(T*, T*);

f(new T, new T);

is not, correct?

Kal
  • 1,309
  • 11
  • 16
  • Your question appears to contradict itself. First you assert that multiple-allocations are exception-safe, then you show an example where this is ostensibly the opposite of what happens. – Lightness Races in Orbit Oct 20 '13 at 00:27
  • @LightnessRacesinOrbit no, I asserted that multiple allocations are not exception safe. I said "make_unique only adds exception safety when you have multiple allocations in an expression" which means it adds nothing for only one allocation. – Kal Oct 20 '13 at 00:32
  • your question lacks any use of `make_unique` or `unique_ptr` in your examples because? – Yakk - Adam Nevraumont Oct 20 '13 at 02:50
  • @Yakk because it doesn't need it – Kal Oct 20 '13 at 02:52
  • `multiple allocations in an expression` but each of those allocations via `new T` _is_ a separate expression, so you have the opposite: one allocation per each of multiple expressions. That the function call is _itself_ an expression containing the other 2 doesn't change that. But aside from the wording, you were on the right track. By my reading, the rule, as formulated by Sutter, is to perform each allocation within its own **statement** or **sequence** them by returning from a separate function call, thereby avoiding weird ordering & leaks: http://www.gotw.ca/gotw/056.htm (oldie-but-goodie) – underscore_d Aug 13 '16 at 11:25

3 Answers3

42

Not only when you have multiple allocations, but whenever you can throw at different places. Consider this:

f(make_unique<T>(), function_that_can_throw());

Versus:

f(unique_ptr<T>(new T), function_that_can_throw());

In the second case, the compiler is allowed to call (in order):

  • new T
  • function_that_can_throw()
  • unique_ptr<T>(...)

Obviously if function_that_can_throw actually throws then you leak. make_unique prevents this case.

And of course, a second allocation (as in your question) is just a special case of function_that_can_throw().

As a general rule of thumb, just use make_unique so that your code is consistent. It is always correct (read: exception-safe) when you need a unique_ptr, and it doesn't have any impact on performance, so there is no reason not to use it (while actually not using it introduces a lot of gotchas).

syam
  • 14,701
  • 3
  • 41
  • 65
  • 3
    Thanks, this is what I was looking for. Sorry to ruin your 2^13 rep. – Kal Oct 20 '13 at 00:33
  • 1
    A little late, but make_unique can itself throw according to cppreference: make_unique "may throw std::bad_alloc or any exception thrown by the constructor of T. If an exception is thrown, this function has no effect." So how is that exception safe? And what happens to a unique_ptr object (named or temp) that is initialized with make_unique, but make_unique throws? – AdmiralAdama Sep 23 '20 at 17:32
  • 1
    Exception safety is not (just) about "no exceptions" but rather about "guaranteeing the right behaviour when you get an exception". The `make_unique` documentation is clear: no effect if there is an exception, this is a strong guarantee (see https://en.wikipedia.org/wiki/Exception_safety). And the `unique_ptr` is not yet constructed so, again, no effect; the exception is simply propagated. – syam Sep 24 '20 at 19:18
22

As of C++17, the exception safety issue is fixed by a rewording of [expr.call]

The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter.

Here indeterminately sequenced means that one is sequenced before another, but it is not specified which.

f(unique_ptr<T>(new T), function_that_can_throw());

Can have only two possible order of execution

  1. new T unique_ptr<T>::unique_ptr function_that_can_throw
  2. function_that_can_throw new T unique_ptr<T>::unique_ptr

Which means it is now exception safe.

Passer By
  • 19,325
  • 6
  • 49
  • 96
7

I'd think you'd be better off comparing things actually using std::unique_ptr<T>:

void f(std::unique_ptr<T>);

f(std::unique_ptr<T>(new T));
f(std::make_unique<T>());

Neither of these calls can leak if there is an exception being thrown. However

void f(std::unique_ptr<T>, std::unique_ptr<T>);

g(std::unique_ptr<T>(new T), std::unique_ptr<T>(new T));
g(std::make_unique<T>(), std::make_unique<T>());

In this case, the version using std::unique_ptr<T> explicitly can leak if an exception is thrown (because the compiler might start evaluating the new-expressions before constructing either of the temporaries).

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380