Exception Safety and Make_Unique

Exception safety and make_unique

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

Exceptionsafety of make_unique: Why is f(new T) exception safe

The reason is that in a function call or similar, the arguments do not induce sequence points (are not "sequenced before"). For example:

do_work(unique_ptr<A>(new A), unique_ptr<B>(new B));

The compiler is allowed to generate code that looks like:

  1. new A
  2. new B // might throw!
  3. Construct unique_ptr<A>
  4. Construct unique_ptr<B>
  5. Call do_work

If new B throws, then you've leaked the A, because no unique_ptr was ever constructed.

Putting the unique_ptr construction into its own function eliminates this problem, because compilers aren't allowed to execute function bodies concurrently (so the "new" and "construct unique_ptr steps need to be done together).


That is, given:

do_work(make_unique<A>(), make_unique<B>())

The compiler must generate code that looks like:

  1. Call make_unique<A>
  2. Call make_unique<B>
  3. Call do_work

or

  1. Call make_unique<B>
  2. Call make_unique<A>
  3. Call do_work

making the leak where there are new'd objects floating around without owning unique_ptrs not possible.

std::unique_ptr and exception safety

  1. All of std::unique_ptr's constructors* are noexcept
  2. malloc won't throw any exception on failure... it will just return nullptr.
  3. I believe your deleter's constructors won't throw anything either.

So you don't need to catch anything, since nothing will be thrown.


*: See C++11 §20.7.1.2.1 unique_ptr constructors [unique.ptr.single.ctor]

How does use of make_unique prevent memory-leak, in C++?

How does use of make_unique prevent memory-leak, in C++?

std::make_unique doesn't "prevent" memory-leak in the sense that it's still possible to write memory leaks in programs that use std::make_unique.

What does std::make_unique is make it easier to write programs that don't have memory leaks.



A or B gets leaked if an exception is thrown there.

Why?

Pre C++17:

Because if you allocate A, then call constructor of B before constructing the std::unique_ptr that was supposed to own A, and the constructor of B throws, then A will leak (or same happens with A and B reversed).

Since C++17:

There's no leak since the scenario described above cannot happen anymore in the shown example.

What does make_unique do that the default unique_ptr does not?

std::make_unique allocates memory, and either successfully returns a valid std::unique_ptr, or throws an exception without leaking memory.

std::unique_ptr(T*) accepts a pointer that was allocated separately. If an exception is thrown before the constructor is called, then there will never have been a unique pointer owning the allocation.


It's possible to fix the (pre-C++17) bug without using std::make_unique:

auto a = std::unique_ptr<A>{new A{}};
auto b = std::unique_ptr<B>{new B{}};
fn(std::move(a), std::move(b));

But if you always use std::make_unique, then you won't accidentally make the mistake of writing leaky version. Furthermore, std::make_unique lets you avoid writing new which allows you to use the rule of thumb "write exactly one delete for each new". 0 new -> 0 delete.

Why does C++ need std::make_unique over forwarded unique_ptr constructor?

I want to summarize discussion with Some programmer dude, StoryTeller - Unslander Monica and Raymond Chen

So, there are 2 reasons:

  1. std::make_unique pairs well with std::make_shared which was introduced earlier so this was easier to learn than new constructor for unique_ptr.
  2. There is possible ambiguity between constructor of unique_ptr and constructor of inner value type (T) if this type have own constructor which takes a pointer to self type. E.g.
struct Inner{
Inner() = default;
Inner(Inner* parent_node): parent(parent_node){}

Inner* parent = nullptr;
};

Inner* parent = make_parent();
// It would be not clear for human which constructor must be called here
std::unique_ptr<Inner> child(parent);

Compiler can deduce which constructor should be called here but it is hard for human. So having function std::make_unique is beneficial because it is clear that all constructors of std::unique_ptr create only unique_ptr and never call inner value constructor while std::make_unique would always call constructor of inner value. This makes code much easier to reason about.

Thanks to everyone for discussion!

Shared_ptr and unique_ptr with exception

Let's look into example as how std::unique_ptr can be used for providing exception safety:

someclass *ptr = new someclass;
...
delete ptr; // in case of exception we have problem

so instead we should use:

std::unique_ptr<someclass> ptr = std::make_unique<someclass>();
... // no problem

simple, safe and no overhead.

So can shared_ptr be used same way to provide exception safety? Yes it can. But it should not, as it is designed for different purpose and would have unnecessary overhead. So it is not mentioned as a tool for such cases, but it does not mean it would not delete owned object if it is the only owner.



Related Topics



Leave a reply



Submit