While porting some C++ code from Microsoft Visual Studio to gcc, I ran into a weird bug, which I eventually boiled down to this:
#include <iostream>
using namespace std;
class Foo {
public:
int data;
Foo(int i) : data(i)
{
cout << "Foo constructed with " << i << endl;
}
Foo(const Foo& f) : data(f.data)
{
cout << "copy ctor " << endl;
}
Foo(const Foo&& f) : data(f.data)
{
cout << "move ctor" << endl;
}
~Foo()
{
cout << "Foo destructed with " << data << endl;
}
};
int Bar(Foo f)
{
cout << "f.data = " << f.data << endl;
return f.data * 2;
}
int main()
{
Foo f1(10);
Foo f2(Bar(std::move(f1)));
}
If I compile and run the above code with Microsoft Visual Studio 2015 Community, I get the following output:
Foo constructed with 10
move ctor
f.data = 10
Foo destructed with 10
Foo constructed with 20
Foo destructed with 20
Foo destructed with 10
However, if I compile and run the code with gcc 6.1.1 and --std=c++14, I get this output:
Foo constructed with 10
move ctor
f.data = 10
Foo constructed with 20
Foo destructed with 10
Foo destructed with 20
Foo destructed with 10
gcc calls the destructor of f
, the argument to Bar()
, after Bar()
returns, while msvc calls the destructor (apparently) before it returns, or at least before the constructor of f2
. When is f
supposed to be destructed, according to the C++ standard?
They are all right; it depends. It seems underspecified in the standard.
From [expr.call]/4 (this wording goes back to C++98);
The lifetime of a parameter ends when the function in which it is defined returns. The initialization and destruction of each parameter occurs within the context of the calling function.
And the CWG#1880;
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.
Both the behaviour of g++ (and clang) and MSVC would be correct, implementations are free to pick one approach over the other.
That all said, if the code you have is dependent on this ordering, I would change it such that the ordering is more deterministic - as you have seen, it leads to subtle bugs.
A simplified example of this behaviour is;
#include <iostream>
struct Arg {
Arg() {std::cout << 'c';}
~Arg() {std::cout << 'd';}
Arg(Arg const&) {std::cout << 'a';}
Arg(Arg&&) {std::cout << 'b';}
Arg& operator=(Arg const&) {std::cout << 'e'; return *this;}
Arg& operator=(Arg&&) {std::cout << 'f'; return *this;}
};
void func(Arg) {}
int main() {
(func(Arg{}), std::cout << 'X');
std::cout << std::endl;
}
Clang and g++ both produce cXd
and MSVC produces cdX
.
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