C++ 编译器如何知道,对于在同一地址构造两次的对象,必须调用第二个析构函数?

C++ 编译器如何知道,对于在同一地址构造两次的对象,必须调用第二个析构函数?,c++,constructor,destructor,placement-new,C++,Constructor,Destructor,Placement New,在下面的代码中,类C中的对象sub构造了两次。第一个构造调用默认的ctorSub(),第二个构造使用placement new在相同的地址中重建此对象 因此析构函数也被称为两次。第一个调用使用对~C()中的Subdtor的直接调用,第二个调用在main()结束后调用,我相信是通过atexit()函数调用的 假设对象sub在同一地址重建,编译器如何知道必须在main()之后调用第二个析构函数?他把这些信息保存在哪里 #include <iostream> using namespace

在下面的代码中,类
C
中的对象
sub
构造了两次。第一个构造调用默认的ctor
Sub()
,第二个构造使用
placement new
在相同的地址中重建此对象

因此析构函数也被称为两次。第一个调用使用对
~C()
中的
Sub
dtor的直接调用,第二个调用在
main()
结束后调用,我相信是通过
atexit()
函数调用的

假设对象
sub
在同一地址重建,编译器如何知道必须在
main()
之后调用第二个析构函数?他把这些信息保存在哪里

#include <iostream>
using namespace std;

struct Table
{
    int i;
    Table(int j) : i(j) {}
};

struct Sub
{
    Table* pTable;
    Sub(int j) { cout << "ctor placement new" << endl; pTable = new Table(j); }
    Sub() { cout << "ctor default" << endl; pTable = 0; }
    ~Sub() { if( pTable ) cout << "dtor placement new" << endl;
             else         cout << "dtor default" << endl;
             delete pTable; pTable = 0; }
};

class C
{
    Sub sub;

    public:
    C() { new (&sub) Sub(10); }
    ~C() { (&sub)->~Sub(); }
};

int main()
{
    C c;
}
#包括
使用名称空间std;
结构表
{
int i;
表(int j):i(j){}
};
结构子
{
表*pTable;

Sub(int j){cout您关于
atexit()
的假设不正确。当对象
C
超出
main()
的范围时,
Sub
的析构函数被
C
的析构函数调用

一个C++析构函数总是调用所有子对象的析构函数。


<>你的代码无论如何都是无效的,因为你在一个已经被构造成对象的内存块(<代码>子/代码>)上调用了放置new操作符。类似于析构函数,C++构造函数总是调用所有子对象的构造函数。

关于<代码> ATEXIT()的假设。
不正确。当对象
C
超出
main()
中的范围时,
sub
的析构函数被
C
的析构函数调用

一个C++析构函数总是调用所有子对象的析构函数。


您的代码仍然无效,因为您正在内存块(
sub
)上调用placement new运算符类似于析构函数,C++构造函数总是调用所有子对象的构造函数。

当C超出范围时,调用它的析构函数。C析构函数将显式调用子析构函数。当C析构函数完成时,子析构函数也将被调用(再次)。,因为所有C++析构函数都会自动调用所有内部对象的析构函数。 本质上,代码

(&sub)->~Sub();
不必要且不正确。您永远不应该显式调用托管对象的析构函数

编辑: 对通过placement new构造的对象显式调用析构函数是有效的。但是,只有当对象未被管理时,才会出现这种情况。例如:

class C
{
    Sub sub[1];

    public:
    C() { new (sub) Sub(10); }
    ~C() { sub->~Sub(); }
};

这不仅是有效的,而且是必要的,因为C的成员属于Sub[1]类型(或者更一般地说是Sub*),因此当C被销毁时,不会显式调用Sub的析构函数。

当C超出范围时,会调用其析构函数。C析构函数将显式调用Sub析构函数。当C析构函数完成时,也会(再次)调用Sub析构函数,因为所有C++析构函数都会自动调用所有内部对象的析构函数。 本质上,代码

(&sub)->~Sub();
不必要且不正确。您永远不应该显式调用托管对象的析构函数

编辑: 对通过placement new构造的对象显式调用析构函数是有效的。但是,只有当对象未被管理时,才会出现这种情况。例如:

class C
{
    Sub sub[1];

    public:
    C() { new (sub) Sub(10); }
    ~C() { sub->~Sub(); }
};

这不仅是有效的,而且是必要的,因为C的成员是Sub[1]类型(或者更一般地说是Sub*),所以当C被销毁时,Sub的析构函数不会被显式调用。

虽然这显然是未定义的行为,但如果你能解释发生了什么,这是非常明显的

创建C类的对象。在此过程中,Sub的默认构造函数被隐式调用。pTable为0。然后,显式调用int构造函数,初始化pTable。然后,在析构函数中,显式调用Sub的析构函数。pTable再次设置为0。然后,在C的析构函数末尾,Sub的析构函数被称为ag艾因,含蓄地


它不是在main的末尾发生的。它是在C的析构函数的末尾发生的。

虽然这显然是未定义的行为,但如果你解释发生了什么,这是非常明显的

创建C类的对象。在此过程中,Sub的默认构造函数被隐式调用。pTable为0。然后,显式调用int构造函数,初始化pTable。然后,在析构函数中,显式调用Sub的析构函数。pTable再次设置为0。然后,在C的析构函数末尾,Sub的析构函数被称为ag艾因,含蓄地


它不是在main的末尾发生的。它发生在C的析构函数的末尾。

其他答案对未定义的行为提出了有效的观点,但我很惊讶没有人提到这个程序应该如何更正

Sub
的析构函数确实需要在此处手动调用,但它需要在新放置之前调用,而不是在销毁期间调用:

class C
{
    Sub sub;

    public:
    C() { 
        (&sub)->~Sub();
        new (&sub) Sub(10); 
    }
};
在当前代码中,您创建了一个
C
的实例,默认情况下,该实例构造了一个
Sub
的实例,然后立即将另一个
Sub
实例放置在旧实例的顶部,而不调用其析构函数。在main
C
的末尾,调用了
Sub
的析构函数ex然后,在已经被破坏的对象上再次调用
sub
的析构函数。这不仅是未定义的行为,如果分配了
sub
的默认构造函数,还将导致内存泄漏

在更正后的代码中,sub是默认构造的,手动销毁,通过placement new构造,然后隐式销毁