According to C++14 [expr.call]/4:
The lifetime of a parameter ends when the function in which it is defined returns.
This seems to imply that a parameter's destructor must run before the code which called the function goes on to use the function's return value.
However, this code shows differently:
#include <iostream>
struct G
{
G(int): moved(0) { std::cout << "G(int)\n"; }
G(G&&): moved(1) { std::cout << "G(G&&)\n"; }
~G() { std::cout << (moved ? "~G(G&&)\n" : "~G()\n"); }
int moved;
};
struct F
{
F(int) { std::cout << "F(int)\n"; }
~F() { std::cout << "~F()\n"; }
};
int func(G gparm)
{
std::cout << "---- In func.\n";
return 0;
}
int main()
{
F v { func(0) };
std::cout << "---- End of main.\n";
return 0;
}
The output for gcc and clang , with -fno-elide-constructors
, is (with my annotations):
G(int) // Temporary used to copy-initialize gparm
G(G&&) // gparm
---- In func.
F(int) // v
~G(G&&) // gparm
~G() // Temporary used to copy-initialize gparm
---- End of main.
~F() // v
So, clearly v
's constructor runs before gparm
's destructor. But in MSVC, gparm
is destroyed before v
's constructor runs.
The same issue can be seen with copy-elision enabled, and/or with func({0})
so that the parameter is direct-initialized. v
is always constructed before gparm
is destructed. I also observed the issue in a longer chain, e.g. F v = f(g(h(i(j())));
did not destroy any of the parameters of f,g,h,i
until after v
was initialized.
This could be a problem in practice, for example if ~G
unlocks a resource and F()
acquires the resource, it would be a deadlock. Or, if ~G
throws, then execution should jump to a catch handler without v
having been initialized.
My question is: does the standard permit both of these orderings? . Is there any more specific definition of the sequencing relationship involving parameter destruction, than just that quote from expr.call/4 which does not use the standard sequencing terms?
When only certain properties of an object or specific indexed array elements are to be used or considered within a function, it can be achieved using the concept of Parameter Destructuring.
The order in which parameters are constructed is unspecified as well, but because function parameters have block scope, although their order of construction is unspecified, destruction is in the reverse order of construction.
To destructure an object inside a function's parameters we place a set of curly braces inside the parentheses of the function. Inside the curly braces, we place the properties of the object which we wish to pick out. Below is an example using an arrow function.
Worse, if the parameter to be destructured is missing, an exception is thrown, probably bringing your app to a screeching halt: sayHelloTimes() // -> Uncaught TypeError: Cannot match against 'undefined' or 'null'...
Actually I can answer my own question... didn't find an answer while searching before writing it, but then searching again afterwards did find an answer (typical huh).
Anyway: this issue is CWG #1880 with the following note:
Notes from the June, 2014 meeting:
WG decided to make it unspecified whether parameter objects are destroyed immediately following the call or at the end of the full-expression to which the call belongs.
although the issue 1880 remains open.
The subject was also visited by P0135 - guaranteed copy-elision which made it implementation-defined, rather than unspecified. In C++17 (N4659) the text is:
It is implementation-defined whether the lifetime of a parameter ends when the function in which it is defined returns or at the end of the enclosing full-expression.
There is more background information here: Late destruction of function parameters
Note: The definition of full-expression can be found in C++14 [intro.execution]/10:
A full-expression is an expression that is not a subexpression of another expression. [...] If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.
So F v { func(0) };
is the enclosing full-expression for gparm
(even though it's a declaration and not an expression!).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With