Откуда компилятор знает, что должен быть вызван второй деструктор для объекта, сконструированного дважды, по одному и тому же адресу? - PullRequest
0 голосов
/ 17 января 2012

В следующем коде объект sub в классе C создается дважды.Первая конструкция вызывает ctor Sub() по умолчанию, а вторая конструкция использует placement new для восстановления этого объекта по тому же адресу.

Поэтому деструкторы также вызываются дважды.Первый вызов использует прямой вызов Sub dtor в ~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 голосов
/ 17 января 2012

Ваше предположение о atexit() неверно. Деструктор для sub вызывается деструктором для C, когда объект c выходит из области видимости в main().

Деструктор C ++ всегда вызывает деструкторы для всех своих подобъектов.

Ваш код в любом случае недопустим, потому что вы вызываете новый оператор размещения для фрагмента памяти (sub), который уже был встроен в объект. Как и деструктор, конструктор C ++ всегда вызывает конструкторы для всех своих подобъектов.

1 голос
/ 17 января 2012

Хотя это явно неопределенное поведение, если вы выясните, что происходит, это довольно очевидно.

Вы создаете объект класса C. В этом процессе конструктор Sub по умолчанию вызывается неявно.pTable равно 0. Затем вы явно вызываете конструктор int, который инициализирует pTable.Затем в деструкторе вы явно вызываете деструктор Sub.pTable снова устанавливается в 0.Затем, в конце деструктора Си, деструктор Суб снова вызывается, неявно.

Это не в конце main, что это происходит.Это происходит в конце деструктора Си.

0 голосов
/ 17 июля 2017

Другие ответы дают правильные замечания о неопределенном поведении, но я удивлен, что никто не упомянул, как эта программа должна быть исправлена.

Деструктор

Sub требует, чтобы вызывался здесь вручную, но он должен вызываться до размещения нового , а не во время уничтожения:

class C
{
    Sub sub;

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

В текущем коде вы создаете экземпляр C, который по умолчанию создает экземпляр Sub, а затем немедленно помещает новости в другой экземпляр Sub поверх старого без вызова его деструктор. В конце основного c вызывается деструктор, который явно вызывает деструктор sub, затем деструктор sub снова вызывается для уже разрушенного объекта. Это не только неопределенное поведение, если выделен конструктор Sub по умолчанию, у вас также будет утечка памяти.

В исправленном коде sub создается по умолчанию, уничтожается вручную, создается путем размещения нового, затем неявно уничтожается, когда c выпадает из области видимости в конце main.

Таким образом, ответ таков: компилятор не знает, что деструктор нужно вызывать, когда вы воссоздаете объект по тому же адресу, потому что для размещения new не требуется объект в качестве первого параметра он принимает void*. У него нет возможности определить, указывает ли этот указатель на существующий объект, поэтому вы должны вызвать деструктор вручную.

0 голосов
/ 17 января 2012

Когда 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 *), поэтому деструктор Sub не будет вызываться явно, когда Cуничтожен.

...