Невиртуальный тривиальный деструктор наследование

#c #inheritance #destructor

#c #наследование #деструктор

Вопрос:

Учитывая, что классу и всем его подклассам не требуется ничего, кроме деструктора по умолчанию, для освобождения своих ресурсов, если они хранятся в переменной точного типа (или указателе на точный тип), может ли подкласс пропускать память, если на него ссылается указатель базового класса, а затем удаляется этим указателем?

Пример:

 #include <memory>

class A {
};

class B : public A {
public:
    B () : pInt(new int) {}
    auto_ptr<int> pInt; // this is what might leak... possibly more will though
};

void will_this_leak () {
    A *pA = new B();
    delete pA;
}
  

Комментарии:

1. Хотел бы я когда-нибудь это сделать? Вероятно, нет. Мне просто любопытно.

2. Ваш вопрос прекрасно иллюстрирует, почему «Зачем вам нужно использовать виртуальные деструкторы?» вопрос находится в списке 10 лучших вопросов по C .

Ответ №1:

«Утечка памяти»? Почему вы говорите именно об утечке памяти?

Опубликованный вами код приводит к неопределенному поведению. В этом случае может произойти что угодно: утечка памяти, форматирование жесткого диска, сбой программы и т.д.

P.S. Я знаю, что существует популярная городская легенда о том, что выполнение полиморфного уничтожения без виртуального деструктора «может привести к утечке памяти». Я не знаю, кто изобрел эту бессмыслицу и почему они решили использовать «утечку памяти» в качестве основного сценария того, что может произойти. На самом деле поведение в этом случае не имеет абсолютно никакого отношения к «утечке памяти». Поведение просто не определено.

С практической точки зрения, в вашем конкретном случае довольно очевидно, что у вашего auto_ptr деструктора нет реальных шансов быть вызванным, поэтому принадлежащая ему память auto_ptr , безусловно, будет утечка. Но опять же, это наименьшая из проблем этого кода.

Комментарии:

1. Особенно — форматированный жесткий диск! Это даже случилось со мной однажды! ( 1, кстати)

Ответ №2:

Не имеет значения, могут ли они протекать или нет. Стандарт C гласит, что удаление производного класса с помощью базового указателя является неопределенным поведением, если у базового нет виртуального деструктора. В частности, из 5.3.5 / 3:

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

Итак, как только вы выполнили такое удаление, ваша программа находится в неопределенном состоянии, и все вопросы об утечках являются спорными.

Ответ №3:

Да, это приведет к утечке. Когда вы удаляете A*, он вызывает ~A(). Поскольку ~A () не является виртуальным, он не будет знать, что ~ B () тоже нуждается в вызове.

Предполагая, конечно, что B наследует от A. Я предполагаю, что это опечатка в вашем вопросе — код в его нынешнем виде не будет компилироваться 🙂

Ответ №4:

На данный момент код демонстрирует неопределенное поведение.

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

 #include <memory>

class A {

    public :
    virtual ~A() {} // This makes the derived sub-object destruction first

};

class B : public A {
public:
    B () : pInt(new int) {}
    auto_ptr<int> pInt; 

    /* There is no need to write any custom destructor
       in this case. auto_ptr will effectively handle deallocating
       the acquired resources from free store.
    */
};

void will_this_leak () {
    A *pA = new B();
    delete pA;
}
  

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