Как работают виртуальные деструкторы?
Несколько часов назад я возился с проблемой утечки памяти, и оказалось, что я действительно неправильно понял некоторые базовые вещи о виртуальных деструкторах! Позвольте мне объяснить мой классный дизайн.
class Base
{
virtual push_elements()
{}
};
class Derived:public Base
{
vector<int> x;
public:
void push_elements(){
for(int i=0;i <5;i++)
x.push_back(i);
}
};
void main()
{
Base* b = new Derived();
b->push_elements();
delete b;
}
Инструмент проверки границ сообщил об утечке памяти в производном классе векторов. И я понял, что деструктор не является виртуальным, и деструктор производного класса не вызывается. И это удивительно исправилось, когда я сделал деструктор виртуальным. Разве вектор не освобождается автоматически, даже если деструктор производного класса не вызывается? Это особенность инструмента BoundsChecker или мое понимание виртуального деструктора неверно?
8 ответов
Удаление объекта производного класса через указатель базового класса, когда базовый класс не имеет виртуального деструктора, приводит к неопределенному поведению.
То, что вы заметили (что часть объекта производного класса никогда не уничтожается и поэтому его члены никогда не освобождаются), вероятно, является наиболее распространенным из многих возможных вариантов поведения, и хорошим примером того, почему важно убедиться, что ваши деструкторы виртуальный, когда вы используете полиморфизм таким образом.
Если базовый класс не имеет виртуального деструктора, то результатом вашего кода будет неопределенное поведение, не обязательно вызываемое неверным деструктором. Вероятно, это то, что диагностирует BoundsChecker.
Хотя это технически не определено, вам все равно нужно знать наиболее распространенный метод сбоя для его диагностики. Этот распространенный метод неудачи - вызвать неправильный деструктор. Я не знаю ни одной реализации, которая потерпит неудачу каким-либо другим способом, хотя по общему признанию я использую только две реализации.
Это происходит по той же причине, по которой "неправильная" функция вызывается при попытке переопределить не виртуальную функцию-член и вызывать ее через базовый указатель.
Если деструктор не является виртуальным, вызывается деструктор Base. Базовый деструктор очищает Базовый объект и завершает работу. Деструктор базового объекта не может знать о производном объекте, это должен быть вызванный производный деструктор, и способ сделать это, как и в случае с любой функцией, - сделать деструктор виртуальным.
Из C++ FAQ Lite: "Когда мой деструктор должен быть виртуальным?" Прочитайте это здесь. (Кстати, C++ FAQ Lite - отличный источник всех ваших вопросов, связанных с C++).
Если вы пришли из C#, то вы правы, задаваясь вопросом, почему вектор не выделяется автоматически. Но в C++ автоматическое управление памятью невозможно, если вы не используете Microsoft Manged Extesions для C++ (C++/CLI).
поскольку в базовом классе нет деструктора, который является виртуальным, объект производного класса никогда не будет освобожден, и тем самым вы утечете память, выделенную для члена векторных данных производного класса.
В C++ тривиальный деструктор представляет собой рекурсивно определенную концепцию - это деструктор, который компилятор написал для вас, когда у каждого члена класса (и каждого базового класса) есть тривиальный деструктор. (Существует похожая концепция, называемая тривиальным конструктором.)
Когда объект с нетривиальным деструктором включен в объект (например, vector
в вашем примере), то деструктор внешнего объекта (как ваш Derived
) в больше не тривиально. Даже если вы не написали деструктор, компилятор C++ автоматически написал деструктор, который вызывает деструкторы всех членов, у которых есть деструкторы.
Таким образом, даже если вы ничего не написали, предостережения от написания не виртуального деструктора все еще применяются.
Деструктор - это функция-член класса, именем которой является то же имя, что и имени класса, и ей предшествует знак тильды (~). Деструктор используется для уничтожения объекта класса, когда объект выходит из области видимости, или вы можете сказать, что вся очистка от уничтожения класса должна выполняться в деструкторе. Вся память, выделяемая при создании объекта в классе, разрушается (или освобождается память), когда объект выходит из области видимости.
Найти более подробную информацию с примером на BoundsCheck