简体   繁体   English

在gcc和MSVC中以不同方式调用函数参数的析构函数

[英]Destructor of a function argument being called differently in gcc and MSVC

While porting some C++ code from Microsoft Visual Studio to gcc, I ran into a weird bug, which I eventually boiled down to this: 在将一些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)));
}

If I compile and run the above code with Microsoft Visual Studio 2015 Community, I get the following output: 如果我使用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

However, if I compile and run the code with gcc 6.1.1 and --std=c++14, I get this output: 但是,如果我使用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 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 . Bar()返回之后,gcc调用f的析构fBar()的参数,而msvc在返回之前调用析构函数(显然),或者至少在f2的构造函数之前调用析构函数。 When is f supposed to be destructed, according to the C++ standard? f应该被破坏,根据C ++标准?

They are all right; 他们没事; it depends. 这取决于。 It seems underspecified in the standard. 标准似乎没有具体说明。

From [expr.call]/4 (this wording goes back to C++98); 来自[expr.call] / 4 (这个措辞可以追溯到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 ; 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. WG决定不确定参数对象是在调用之后立即销毁还是在调用所属的完整表达式结束时销毁。

Both the behaviour of g++ (and clang) and MSVC would be correct, implementations are free to pick one approach over the other. g ++(和clang)和MSVC的行为都是正确的,实现可以自由选择一种方法而不是另一种方法。

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 . Clang和g ++都产生cXd和MSVC产生cdX

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM