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

发布于 2024-12-27 15:31:10 字数 1032 浏览 0 评论 0原文

在下面的代码中,类 C 中的对象 sub 被构造了两次。第一个构造调用默认构造函数 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;
}

In the code that follows, the object sub in class C is constructed twice. The first construction calls the default ctor Sub() and the second construction uses placement new to reconstruct this object in the same address.

Therefore the destructors are also called twice. The first call uses the direct call to the Sub dtor in ~C() and the second call is invoked after the end of main(), I believe, by the atexit() function.

Given that the object sub is reconstructed at the same address, how does the compiler knows that the second destructor must be called after main()? Where does he keep this information?

#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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

燃情 2025-01-03 15:31:10

您对 atexit() 的假设是不正确的。当对象 c 超出 main()C 的析构函数将调用 sub 的析构函数>。

C++ 析构函数总是为其所有子对象调用析构函数。

无论如何,您的代码都是无效的,因为您正在对已经构造成对象的内存块 (sub) 调用放置 new 运算符。与析构函数类似,C++ 构造函数总是为其所有子对象调用构造函数。

Your assumption about atexit() is incorrect. The destructor for sub is called by the destructor for C when the object c goes out of scope in main().

A C++ destructor always calls destructors for all its sub-objects.

Your code is invalid anyway, because you're calling the placement new operator on a chunk of memory (sub) that has already been constructed into an object. Similarly to the destructor, a C++ constructor always calls constructors for all its sub-objects.

肩上的翅膀 2025-01-03 15:31:10

虽然这显然是未定义的行为,但如果你推理出发生了什么,那就很明显了。

您创建了类 C 的对象。在此过程中,隐式调用 Sub 的默认构造函数。 pTable 为 0。然后,显式调用 int 构造函数来初始化 pTable。然后,在析构函数中,显式调用 Sub 的析构函数。 pTable 再次设置为 0。然后,在 C 的析构函数结束时,再次隐式调用 Sub 的析构函数。

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

Although this is clearly undefined behavior, if you reason out what's happening, it's pretty obvious.

You create an object of class C. In that process, the default constructor of Sub is called implicitly. pTable is 0. Then, you explicitly call the int constructor, which initializes pTable. Then, in the destructor, you explicitly call Sub's destructor. pTable is set to 0 again. Then, at the end of C's destructor, Sub's destructor is called again, implicitly.

It's not at the end of main that it's happening. It's happening at the end of C's destructor.

国粹 2025-01-03 15:31:10

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

本质上,该代码

(&sub)->~Sub();

是不必要的,而且是不正确的。您永远不应该显式调用托管对象的析构函数。

编辑:
在通过放置 new 构造的对象上显式调用析构函数是有效的。然而,只有当对象不受管理时才会出现这种情况。例如:

class C
{
    Sub sub[1];

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

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

When c goes out of scope, its destructor is called. The C destructor will explicitly call the sub destructor. When the C destructor is done, the sub destructor will also be called (again), because all C++ destructors automatically call the destructors of all their internal objects.

Essentially, the code

(&sub)->~Sub();

is unnecessary, and incorrect. You should never explicitly call the destructor of a managed object.

Edit:
It's valid to explicitly call the destructor on an object that was constructed via placement new. However this is only the case when the object is not managed. For example:

class C
{
    Sub sub[1];

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

This is not only valid, but necessary because the member of C is of type Sub[1] (or more generally Sub*), so Sub's destructor will not be called explicitly when C is destroyed.

油焖大侠 2025-01-03 15:31:10

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

Sub 的析构函数确实需要在这里手动调用,但是需要在放置 new 之前调用,而不是在销毁期间调用

class C
{
    Sub sub;

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

:当前代码您创建一个 C 实例,默认构造一个 Sub 实例,然后立即放置消息另一个 Sub 实例code> 位于旧代码之上,而不调用其析构函数。在 main c 的析构函数被调用的最后,显式调用 sub 的析构函数,然后 sub 的析构函数被再次调用已经被破坏的物体。这不仅是未定义的行为,如果分配了 Sub 的默认构造函数,您也会遇到内存泄漏。

在更正后的代码中,sub 是默认构造的,手动析构,通过放置 new 构造,然后当 c 在 main 末尾超出范围时隐式析构。

所以答案是编译器知道当您在同一地址重新创建对象时需要调用析构函数,因为放置 new 不接受对象它采用 void* 作为其第一个参数。它无法确定该指针是否指向现有对象,因此您必须手动调用析构函数。

The other answers make valid points about undefined behavior, but I'm surprised no one's mentioned how this program should be corrected.

Sub's destructor does need to be called manually here, but it needs to be called before the placement new, not during destruction:

class C
{
    Sub sub;

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

In the current code you create an instance of C, which default constructs an instance of Sub then immediately placement news another instance of Sub on top of the old one without calling its destructor. At the end of main c's destructor gets called, which calls sub's destructor explicitly, then sub's destructor gets called again on the already destructed object. Not only is this undefined behavior, if Sub's default constructor allocated you would also have a memory leak.

In the corrected code sub is default constructed, destructed manually, constructed via placement new, then destructed implicitly when c falls out of scope at the end of main.

So the answer is that the compiler doesn't know that the destructor needs to be called when you recreate the object at the same address because placement new doesn't take an object as its first parameter, it takes a void*. There's no way for it to determine whether that pointer is pointing to an existing object, so you have to call the destructor manually.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文