c – 在gcc和MSVC中不同地调用函数参数的析构函数

在将一些C代码从Microsoft Visual Studio移植到 gcc时,我遇到了一个奇怪的bug,我最终归结为:
#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之前.根据C标准,什么时候f被破坏?

解决方法

他们没事这取决于.标准似乎不太明确.

[expr.call]/4(这个措辞可以追溯到C98);

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.

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.

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.

相关文章

/** C+⬑ * 默认成员函数 原来C++类中,有6个默认成员函数: 构造函数 析构函数 拷贝...
#pragma once // 1. 设计一个不能被拷贝的类/* 解析:拷贝只会放生在两个场景中:拷贝构造函数以及赋值运...
C类型转换 C语言:显式和隐式类型转换 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译...
//异常的概念/*抛出异常后必须要捕获,否则终止程序(到最外层后会交给main管理,main的行为就是终止) try...
#pragma once /*Smart pointer 智能指针;灵巧指针 智能指针三大件//1.RAII//2.像指针一样使用//3.拷贝问...
目录&lt;future&gt;future模板类成员函数:promise类promise的使用例程:packaged_task模板类例程...