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?
X f(A a)
which returnX(a)
, if I doX x(f(A()))
, compiler will likely constructx
in place, but ifA()
was meant to be destroyed at the end off()
, how would this be done (you would need to constructX
froma
, then destroya
, then copyX
, no?)? – PneumoX x(f(A()))
,A()
is the argument, not the parameter. The argument is destroyed at the end of the full-expression. The parameter is the symbola
insidef
, which is copy-initialized from the argument. If that copy-initialization is elided then the argument is never destroyed ; there is the constructionA()
and the destruction of the parameter. – Auriax
's constructor, nota
's - Ifa
has to be destroyed at the end off
, how can you constructX
in-place usinga
without modifying the order in the observable behavior (but maybe copy-elision is allowed to do that?)? What I try to say is that without elision, the behavior should be (I think)X(const A&)
->~A()
->X(X&&)
, and with the elision it becomesX(const A&)
->~A()
, so in the first case,a
is destroyed before the construction (by copy) ofx
, while in the second casea
is destroyed after - But maybe this is allowed? – PneumoX(A&) -> ~A -> X(X&&)
order, gcc usesX(A&) -> X(X&&) -> ~A
. So MSVC's elision does change the ordering between~A
and construction of the caller'sX
. But I think that is OK – Auria