在将一些C ++代码从Microsoft Visual Studio移植到gcc时,我遇到了一个奇怪的错误,我最终将其归结为:
#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)));
}
如果我使用Microsoft Visual Studio 2015社区编译并运行上述代码,我会得到以下输出:
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
但是,如果我使用gcc 6.1.1和--std = c ++ 14编译并运行代码,我会得到这个输出:
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在f
返回后调用Bar()
的析构函数,Bar()
的参数,而msvc在返回之前调用析构函数(显然),或者至少在构造函数之前调用析构函数f2
。什么时候f
应该被破坏,根据C ++标准?
答案 0 :(得分:8)
他们没事;这取决于。它似乎在标准中没有具体说明。
从[expr.call]/4(这个措辞可以追溯到C ++ 98);
参数的生命周期在定义它的函数返回时结束。每个参数的初始化和销毁发生在调用函数的上下文中。
WG决定不指定参数对象是在调用之后立即销毁还是在调用所属的完整表达式结束时销毁。
g ++(和clang)和MSVC的行为都是正确的,实现可以自由选择一种方法而不是另一种方法。
所有人都说,如果您拥有的代码依赖于这种排序,我会改变它以使排序更具确定性 - 如您所见,它会导致细微的错误。
此行为的简化示例是;
#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和g ++都生成cXd
,MSVC生成cdX
。