In the following code (build on gcc 9.2 with -std=c++14 -Wall -fno-elide-constructors
):
struct Noisy {
Noisy() { std::cout << "Default construct [" << (void*)this << "]\n"; }
Noisy(const Noisy&) { std::cout << "Copy construct [" << (void*)this << "]\n"; }
Noisy(Noisy&&) { std::cout << "Move construct [" << (void*)this << "]\n"; }
Noisy& operator=(const Noisy&) { std::cout << "Copy assignment" << std::endl; return *this; }
Noisy& operator=(Noisy&&) { std::cout << "Move assignment" << std::endl; return *this; }
~Noisy() { std::cout << "Destructor [" << (void*)this << "]\n"; }
};
Noisy f() {
Noisy x;
return x;
}
Noisy g(Noisy y) {
return y;
}
int main(void) {
Noisy a;
std::cout << "--- f() ---\n";
Noisy b = f();
std::cout << "b [" << (void*)&b << "]\n";
std::cout << "--- g(a) ---\n";
Noisy c = g(a);
std::cout << "c [" << (void*)&c << "]\n";
std::cout << "---\n";
return 0;
}
Which produces this outcome:
Default construct [0x7ffc4445737a]
--- f() ---
Default construct [0x7ffc4445735f]
Move construct [0x7ffc4445737c]
Destructor [0x7ffc4445735f]
Move construct [0x7ffc4445737b]
Destructor [0x7ffc4445737c]
b [0x7ffc4445737b]
--- g(a) ---
Copy construct [0x7ffc4445737e]
Move construct [0x7ffc4445737f]
Move construct [0x7ffc4445737d]
Destructor [0x7ffc4445737f]
Destructor [0x7ffc4445737e]
c [0x7ffc4445737d]
---
Destructor [0x7ffc4445737d]
Destructor [0x7ffc4445737b]
Destructor [0x7ffc4445737a]
Why does the copy of the local Noisy object [0x7ffc4445735f]
in f()
gets destructed right after it has been moved into f
's return address (and before construction of b
starts); while the same does not seem to happen for g()
?
I.e. in the latter case (when g()
executes), the local copy of the function argument Noisy y
, [0x7ffc4445737e]
gets destroyed only after c
is ready to be constructed. Shouldn't it have been destroyed right after it was moved into g
's return address, same as it happened for f()
?
These are the variables for the addresses in the output:
0x7ffc4445737a a
0x7ffc4445735f x
0x7ffc4445737c return value of f()
0x7ffc4445737b b
0x7ffc4445737e y
0x7ffc4445737f return value of g()
0x7ffc4445737d c
I interpret the question as: you are highlighting the following two points:
x
is destroyed before b
is constructedy
is destroyed after c
is constructedand ask why both cases didn't behave the same.
The answer is: in C++14 the standard specified in [expr.call]/4 that y
should be destroyed when the function returns. However it was not clearly specified at exactly which stage of the function return this meant. A CWG issue was raised.
As of C++17 the specification is now that it's implementation-defined whether y
is destroyed at the same time as local variables of the function, or at the end of the full-expression containing the function call. It turned out that the two cases could not be reconciled because it would be a breaking ABI change (think what happens if the destructor of y
throws an exception); and also the Itanium C++ ABI specifies destruction at the end of the full-expression.
We can't definitively say that g++ -std=c++14
doesn't conform to C++14 due to the ambiguity of the C++14 wording, however in any case it's not going to be changed now due to the ABI issue.
For an explanation with links to the Standard and the CWG report, see this question: Sequencing of function parameter destruction and also Late destruction of function parameters