0

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

因此析构函数也被调用了两次。第一次调用使用对Subdtor in的直接调用~C(),第二次调用是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;
}
4

4 回答 4

4

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.

于 2012-01-17T18:11:18.810 回答
1

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.

于 2012-01-17T18:26:01.513 回答
0

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.

于 2012-01-17T18:12:38.027 回答
0

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.

于 2017-07-17T20:55:04.360 回答