Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::make_shared() change in C++17

In cppref, the following holds until C++17:

code such as f(std::shared_ptr<int>(new int(42)), g()) can cause a memory leak if g gets called after new int(42) and throws an exception, while f(std::make_shared<int>(42), g()) is safe, since two function calls are never interleaved.

I'm wondering which change introduced in C++17 renders this no longer applicable.

like image 340
Lingxi Avatar asked Feb 17 '18 14:02

Lingxi


People also ask

What does Make_shared return in C++?

It constructs an object of type T passing args to its constructor, and returns an object of type shared_ptr that owns and stores a pointer to it.

What happens if Make_shared fails?

So, if you throw exception from your class' constructor, then std::make_shared will throw it too. Besides exceptions thrown from constructor, std::make_shared could throw std::bad_alloc exception on its own. Therefore, you don't need to check if the result of std::make_shared is nullptr .

What is boost :: Make_shared?

The header file <boost/make_shared. hpp> provides a family of overloaded function templates, make_shared and allocate_shared , to address this need. make_shared uses the global operator new to allocate memory, whereas allocate_shared uses an user-supplied allocator, allowing finer control.

Why is Make_shared more efficient?

One reason is because make_shared allocates the reference count together with the object to be managed in the same block of memory. OK, I got the point. This is of course more efficient than two separate allocation operations.


2 Answers

The evaluation order of function arguments are changed by P0400R0.

Before the change, evaluation of function arguments are unsequenced relative to one another. This means evaluation of g() may be inserted into the evaluation of std::shared_ptr<int>(new int(42)), which causes the situation described in your quoted context.

After the change, evaluation of function arguments are indeterminately sequenced with no interleaving, which means all side effects of std::shared_ptr<int>(new int(42)) take place either before or after those of g(). Now consider the case where g() may throw.

  • If all side effects of std::shared_ptr<int>(new int(42)) take place before those of g(), the memory allocated will be deallocated by the destructor of std::shared_ptr<int>.

  • If all side effects of std::shared_ptr<int>(new int(42)) take place after those of g(), there is even no memory allocation.

In either case, there is no memory leak again anyway.

like image 146
xskxzr Avatar answered Oct 06 '22 18:10

xskxzr


The P0145R3 paper (which was accepted into C++17) refines the order of evaluation of several C++ constructs, including

Postfix expressions are evaluated from left to right. This includes functions calls and member selection expressions

Specifically, the paper adds the following text to 5.2.2/4 paragraph of the standard:

The postfix-expression is sequenced before each expression in the expression-list and any default argument. Every value computation and side effect associated with the initialization of a parameter, and the initialization itself, is sequenced before every value computation and side effect associated with the initialization of any subsequent parameter.

like image 20
lisyarus Avatar answered Oct 06 '22 17:10

lisyarus