#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;
}
С внесенными выше изменениями не должно быть никакого неопределенного поведения или утечек памяти.