Нужно ли иметь виртуальный деструктор, если производный класс содержит только автоматические переменные-члены? - PullRequest
0 голосов
/ 25 мая 2019
struct base
{
    base(){}
    ~base() { cout << "base destructor" << endl; }
};

struct derived : public base
{
    derived() : base() { vec.resize(200000000); }
    ~derived() { cout << "derived destructor" << endl; }
    vector<int> vec;
};

int main()
{
    base* ptr = new derived();
    delete ptr;

    while (true)
    {

    }
}

Приведенный выше код пропадает из-за операции удаления, не вызывающей деструктор производного объекта.Но ...

struct base
{
    base() {}
    ~base() { cout << "base destructor" << endl; }
};

struct derived : public base
{
    derived() : base() {}
    ~derived() { cout << "derived destructor" << endl; }
    int arr[200000000];
};

int main()
{
    base* ptr = new derived();
    delete ptr;

    while (true)
    {

    }
}

Во втором случае память не протекает, несмотря на то, что базовый деструктор только вызывается.Итак, я предполагаю, что безопасно не иметь базового деструктора, если все мои члены являются автоматическими переменными?Разве член 'arr' в производном классе никогда не выходит из области видимости, когда деструктор производного объекта не вызывается?Что происходит за кулисами?

Ответы [ 3 ]

3 голосов
/ 25 мая 2019

ДА!

Я вижу, что вы думаете "практически" о том, какие разрушения могут быть пропущены.Учтите, что деструктор вашего производного класса - это не просто тело деструктора, которое вы пишете - в этом контексте вам также необходимо учитывать уничтожение членов, и ваше предложение может не привести к уничтожению вектора (потому что процедура, фактически не разрушающая ваш объект, не будетдаже знаю, что есть производная часть для рассмотрения).В векторе есть динамически размещаемое содержимое, которое будет вытекать.

Однако нам даже не нужно заходить так далеко.Поведение вашей программы не определено, период, конец истории.Оптимизатор может делать предположения на основании того, что ваш код действителен.Если это не так, вы можете и должны ожидать странного дерьма, которое может не соответствовать тому, как должны работать ваши ожидания от компьютера.Это потому, что C ++ - это абстракция, компиляция сложна, и вы заключили договор с языком.

3 голосов
/ 25 мая 2019

Всегда необходимо иметь виртуальный деструктор в базовом классе, если производный объект когда-либо удаляется через указатель на эту базу. В противном случае поведение программы не определено. В любом другом случае нет необходимости иметь виртуальный деструктор. Не имеет значения, какие члены есть в классе.

1 голос
/ 25 мая 2019

Нет необходимости иметь утечку памяти и все равно вызывать UB. Утечка памяти является своего рода ожидаемым UB, если ваш производный класс не тривиален. Пример:

#include <iostream>

class  Field {
public:
    int *data;
    Field() : data(new int[100]) {} 
    ~Field() { delete[] data; std::cout << "Field is destroyed"; }
};

class Base {
    int c;
};

// Derived class, contains a non-trivial non-static member                 
class Core : public Base 
{
    Field A;
};

int main()
{
    Base *base = new Core;
    delete base;  // won't delete Field
}

Стандарт C ++, [expr.delete], пункт 3 гласит (издание 2014 г.)

В первом варианте (удалить объект), если статический тип удаляемый объект отличается от его динамического типа, статического тип должен быть базовым классом динамического типа объекта, который будет удаляется и статический тип должен иметь виртуальный деструктор или поведение не определено. Во втором варианте (удаление массива), если динамический тип удаляемого объекта отличается от его статического типа, поведение не определено.

В действительности, если базовый класс тривиален, все поля тривиальны, а производный класс не содержит нестатических или нетривиальных членов, можно утверждать, что эти классы равны, но мне еще предстоит найти способ, как докажи это через стандарт. Это скорее IB, чем UB.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...