Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ allocating shared_ptr with C++11 (std::shared_ptr): Is it still bad to initialize the shared_ptr into a temporary variable?

I was reading this answer and the author refers to boost best practices which says:

Avoid using unnamed shared_ptr temporaries to save typing; to see why this is dangerous, consider this example:

void f(shared_ptr<int>, int); 
int g();

void ok() {
    shared_ptr<int> p(new int(2));
    f(p, g());
}

void bad() {
    f(shared_ptr<int>(new int(2)), g());
}

The function ok follows the guideline to the letter, whereas bad constructs the temporary shared_ptr in place, admitting the possibility of a memory leak. Since function arguments are evaluated in unspecified order, it is possible for new int(2) to be evaluated first, g() second, and we may never get to the shared_ptr constructor if g throws an exception. <...>

The exception safety problem described above may also be eliminated by using the make_shared or allocate_shared factory functions defined in boost/make_shared.hpp. These factory functions also provide an efficiency benefit by consolidating allocations.

I guess I will start using make_shared, but I was wondering if this bit of advice still applies to C++11 shared_ptr. I ask because I don't really fully understand why it is that a throwing g() would prevent the ctor from getting called.

like image 442
Steven Lu Avatar asked Aug 28 '13 02:08

Steven Lu


2 Answers

I don't think anything has changed with respect to the order of evaluation in C++11. That is, using std::make_shared<T>(...) is the better option with respect to safety in addition to requiring only one memory allocation rather than two.

With respect to what the problem: the following evaluation of f() argument is perfectly valid:

  1. $tmp = new int(1) (using $tmp to indicate a compiler provided, temporary variable)
  2. g()
  3. std::shared_ptr<int>($tmp)

Now, if g() throws, the evaluation of the other parts is never executed, i.e., the std::shared_ptr<int> is never constructed and $tmp is leaked.

like image 32
Dietmar Kühl Avatar answered Oct 24 '22 22:10

Dietmar Kühl


Yes, C++11's shared_ptr works the same way.

I ask because I don't really fully understand why it is that a throwing g() would prevent the ctor from getting called.

What aren't you understanding? It's an order of operations problem, and the standard doesn't require a specific order. Let's unpack the statement into a sequence of expressions:

auto __temp = new int(2);
auto &&__temp2 = g();
auto __temp3 = shared_ptr<int>(__temp);

Do you see the problem now? If g throws, then __temp3 will never be initialized. Therefore, __temp will be leaked.

The C++ standard does not require that the statement be unpacked in this way. But it does not forbid it either. The compiler is allowed the freedom to order these independent expressions however it sees fit.

like image 199
Nicol Bolas Avatar answered Oct 24 '22 21:10

Nicol Bolas