C++:在对象范围外调用析构函数

C++: Destructor being called outside object scope?

本文关键字:调用 析构函数 范围 对象 C++      更新时间:2023-10-16

更新1:根据建议添加了打印"this"。

更新2:拆分为多个文件,尝试阻止gcc进行优化。

更新3:记录复制构造函数并输入添加函数。

更新4:添加了Clang的输出和main中的第二个cout。

我期望参数析构函数作为函数中的最后一条语句被调用。从此以后,我期望从下面的代码中得到以下输出。

default constructor: 008DFCF8
other constructor: 008DFCEC
copy constructor: 008DFBC0
in member add
destroying: 008DFBC0
copy constructor: 008DFBB8
copy constructor: 008DFBB4
in function add
destroying: 008DFBB4
destroying: 008DFBB8
3 == 3
end of main
destroying: 008DFCEC
destroying: 008DFCF8

使用MSVC(Visual Studio)时,输出与预期的一样。但是GCC(4.8.2-19ubuntu1)输出以下内容,表明函数参数的析构函数是在main()中的第一个cout语句之后但在最后一个语句之前调用的。

default constructor: 0x7fff2fcea510
other constructor: 0x7fff2fcea520
copy constructor: 0x7fff2fcea550
in member add
copy constructor: 0x7fff2fcea540
copy constructor: 0x7fff2fcea530
in function add
3 == 3
destroying: 0x7fff2fcea530
destroying: 0x7fff2fcea540
destroying: 0x7fff2fcea550
end of main
destroying: 0x7fff2fcea520
destroying: 0x7fff2fcea510

对于那些好奇clang++(3.4-1ubuntu3)输出什么的人来说。

default constructor: 0x7fff52cf9878
other constructor: 0x7fff52cf9870
copy constructor: 0x7fff52cf9860
copy constructor: 0x7fff52cf9858
in function add
3 == copy constructor: 0x7fff52cf9850
in member add
3
destroying: 0x7fff52cf9850
destroying: 0x7fff52cf9858
destroying: 0x7fff52cf9860
end of main
destroying: 0x7fff52cf9870
destroying: 0x7fff52cf9878

问题:

  1. 我最初的怀疑是GCC在内联函数?如果这是真的,有没有办法禁用这种优化
  2. C++规范中的哪个部分允许在main中的cout之后调用析构函数?特别令人感兴趣的是关于内联的规则(如果相关)以及何时调度析构函数

// Test.h
#ifndef __TEST_H__
#include <iostream>
using namespace std;
class Test
{
public:
    int val;
    Test(Test const &a) : val(a.val)
    {
        cout << "copy constructor: " << this << endl;
    }
    Test() : val(1)
    {
        cout << "default constructor: " << this << endl;
    }
    Test(int val) : val(val)
    {
        cout << "other constructor: " << this << endl;
    }
    ~Test()
    {
        cout << "destroying: " << this << endl;
    }
    int add(Test b);
};
#endif

// Add.cpp
#include "Test.h"
int Test::add(Test b)
{
    cout << "in member add" << endl;
    return val + b.val;
}
int add(Test a, Test b)
{
    cout << "in function add" << endl;
    return a.val + b.val;
}

// Main.cpp
#include "Test.h"
int add(Test a, Test b);
int main()
{
    Test one, two(2);
    cout << add(one, two) << " == " << one.add(two) << endl;
    cout << "end of main" << endl;
    return 0;
}

使用为GCC编译

g++ -c Add.cpp -o Add.o ; g++ -c Main.cpp -o Main.o ; g++ Add.o Main.o -o test

对于函数参数析构函数的确切调用时间,C++标准似乎有点模棱两可。C++03和C++11都在5.2.2/4中说"函数调用"(增加了强调):

当参数所在的函数定义的回报。每个参数的初始化和销毁发生在调用函数的上下文中。

因此,参数的析构函数在概念上不会出现在函数的右大括号处。这是我不知道的事情。

该标准给出了一个注释,解释了这意味着如果参数的析构函数抛出,那么只考虑调用函数或"更高"的异常处理程序(特别是,即使被调用函数有"函数尝试块",也不考虑)。

虽然我认为目的是为了MSVC行为,但我可以看到有人可能会如何解释允许GCC行为的读数。

再说一遍,也许这是GCC中的一个错误?

在调用"add(a,b)"和成员add(b)时创建临时对象。我认为您在gcc的情况下看到的是,当add()函数(参数)返回时,这些函数中的局部变量会被破坏。最后两行"done"表示变量"one"answers"two"。

VC是不同的,但这并没有错,它只是表明两个编译器正在以不同的方式优化代码。

不要只打印"done",也可以尝试打印值"this"。在构造函数中也打印"this",然后您可以看到构造函数和析构函数调用是如何配对的。

糟糕——我把VC和GCC搞混了。VC先打印"done"三次,大概是因为add()参数被破坏了,而GCC最后打印,可能是因为它内联了add函数。

考虑这一行:

cout << add(one, two) << " == " << one.add(two) << endl;

写成:

cout << add(one, two);
cout << " == " << one.add(two) << endl;

这会改变GCC的打印输出吗?

或者那样:

auto i = add(one, two);
cout << i << " == ";
auto j = one.add(two)
cout << j << endl;

我认为这是关于的副作用(而不是内联)。VC似乎提前调度了副作用(临时对象的销毁),而GCC则将其调度在语句末尾-;


增加报价:

临时对象生存期

临时对象是在各种情况下创建的:绑定对prvalue的引用,从函数返回prvalue,强制转换为prvalue,抛出异常,输入异常处理程序,以及一些初始化在任何情况下,所有临时物品都会被销毁评估完整表达式的最后一步包含创建它们的点,如果多个临时工被创建,它们按照与创造的顺序。即使评估以抛出异常。

在我看来,这代表了GCC和VC(尤其是在销毁后打印"3==3",这对我来说很奇怪)。