28

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.

Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • 1
    The answer on the similar question is here https://stackoverflow.com/questions/38501587/what-are-the-evaluation-order-guarantees-introduced-by-c17 – 273K Feb 17 '18 at 14:49
  • So... we don't need `make_shared` anymore? (though it's still handy) – user541686 Feb 18 '18 at 01:28
  • @Mehrdad There are other benefits. Trade-offs are discussed on the cppref page I linked. – Lingxi Feb 18 '18 at 03:50

2 Answers2

20

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.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
lisyarus
  • 15,025
  • 3
  • 43
  • 68
  • 13
    Wow, having not followed C++17 at all, this is an almost-mindblowingly substantial change! Of course if we all did our jobs properly it ought to be entirely transparent to us but, still... wow. – Lightness Races in Orbit Feb 17 '18 at 14:51
  • 2
    Hang on, the open-std.org page summary doesn't match the paper's text you quoted. One says that the order is now indeterminate (not unspecified) - it means the evaluations can't be interleaved (which I thought was already the case in C++11). The paper itself does use the term "subsequent" which suggests the order is quite determinate. Which is it! – Lightness Races in Orbit Feb 17 '18 at 14:53
  • @LightnessRacesinOrbit Yep, this makes me puzzled too. The paper itself mentions left-to-right as an informal suggestion, but the other link says it's indeterminate. I'm not quite sure how to parse the patch to 5.2.2/4, though. – lisyarus Feb 17 '18 at 14:56
  • 4
    Okay I think it's the nesting behaviour that's improved. So we are guaranteed that, when `g()` happens, _if_ the `new` happened already then the `shared_ptr` construction has too. https://stackoverflow.com/a/38501596/560648 However the order between the `shared_ptr` construction and `g()` is still not known. – Lightness Races in Orbit Feb 17 '18 at 15:17
  • 3
    @lisyarus: I doubt the order of evaluation of the arguments would change simply because gcc/clang use right-to-left while MSVC use left-to-right and I doubt either would be keen in changing this; it is likely that software out there relies on their current behavior. – Matthieu M. Feb 17 '18 at 20:02
  • This is so subtle and intricate. I don't think it's good practice to write code that relies on this. – Lingxi Feb 18 '18 at 04:00
  • 2
    I think xskxzr answers my question more directly and clearly in human language as opposed to arcane standard text, so I will accept his answer instead. Still, your answer along with the discussion in the comment section are very useful, which well deserve an up-vote :) – Lingxi Feb 18 '18 at 04:09
20

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.

xskxzr
  • 12,442
  • 12
  • 37
  • 77