Я хорошо разбираюсь в большинстве теорий OOP, но единственное, что меня сильно смущает, - это виртуальные деструкторы.
Я думал, что деструктор всегда вызывается независимо от того, что и для каждого объекта в цепочке.
Когда вы хотите сделать их виртуальными и почему?
Каждый деструктор вниз вызывается несмотря ни на что. virtual гарантирует, что он начинается сверху, а не посередине.
связанный вопрос: Когда не следует использовать виртуальные деструкторы?
@MooingDuck, это несколько вводящий в заблуждение комментарий.
@EuriPinhollow Mind разрабатывает?
@FranklinYu, хорошо, что вы спросили, потому что теперь я не вижу никаких проблем с этим комментарием (кроме попытки дать ответ в комментариях).
@FranklinYu Я, вероятно, думал о том, что должно быть более конкретное утверждение (т.е. когда разрушение может начинаться в середине дерева наследования, а не сверху), но это то, что ответы уже подробно описаны.
Меня также смущает ответ @MooingDuck. Разве это не должно быть вверх вместо вниз, если вы используете понятие подкласса (ниже) и суперкласса (выше)?
@Nibor: Да, если вы используете это понятие. Около половины людей, с которыми я разговариваю, рассматривают суперклассы как «верхние», а половина - как «нижние», так что оба являются противоречивыми стандартами, что сбивает все с толку. Я думаю, что суперкласс «выше» встречается немного чаще, но меня этому не учили :(
эта статья может помочь. medium.com/@tunvirrahmantusher/…





Сделайте деструктор виртуальным, когда ваш класс полиморфен.
Виртуальные деструкторы полезны, когда вы потенциально можете удалить экземпляр производного класса с помощью указателя на базовый класс:
class Base
{
// some virtual methods
};
class Derived : public Base
{
~Derived()
{
// Do some important cleanup
}
};
Здесь вы заметите, что я не объявлял деструктор Base как virtual. Теперь давайте посмотрим на следующий фрагмент:
Base *b = new Derived();
// use b
delete b; // Here's the problem!
Поскольку деструктор Base - это не virtual, а b - это Base*, указывающий на объект Derived, delete b имеет неопределенное поведение:
[In
delete b], if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.
В большинстве реализаций вызов деструктора будет разрешен как любой невиртуальный код, что означает, что будет вызываться деструктор базового класса, но не деструктора производного класса, что приведет к утечке ресурсов.
Подводя итог, всегда делайте деструкторы базовых классов virtual, когда они предназначены для полиморфных манипуляций.
Если вы хотите предотвратить удаление экземпляра с помощью указателя базового класса, вы можете сделать деструктор базового класса защищенным и невиртуальным; при этом компилятор не позволит вам вызвать delete по указателю базового класса.
Вы можете узнать больше о виртуальности и деструкторе виртуального базового класса в эта статья от Херба Саттера.
Это могло бы объяснить, почему у меня были массовые утечки на фабрике, которую я сделал раньше. Теперь все имеет смысл. Спасибо
Что ж, это плохой пример, поскольку нет членов данных. Что, если Base и Derived имеют переменные автоматического хранения все? т.е. в деструкторе нет «специального» или дополнительного пользовательского кода. Можно ли вообще перестать писать деструкторы? Или у производного класса по-прежнему будет утечка памяти?
@ workmad3 не виртуальный для этого не нужен, не так ли? Может быть, бесполезно, но отсутствовать не обязательно.
когда есть только простые типы данных (например, без указателей), ожидаем ли мы по-прежнему утечки памяти, если не будем использовать виртуальные деструкторы?
@ransh: как @bobobobo уже сказал в предыдущем комментарии, даже если классы не имеют переменных-членов, вызов delete на Base*, который указывает на объект Derived, является неопределенным поведением. Это означает, что программа может делать что угодно, включая утечку памяти.
Из статьи Херба Саттера: «Рекомендация №4: деструктор базового класса должен быть либо общедоступным и виртуальным, либо защищенным и невиртуальным».
Также из статьи - «если вы удалите полиморфно без виртуального деструктора, вы вызовете ужасный призрак« неопределенного поведения », призрак, которого я лично не хотел бы встречать даже в умеренно хорошо освещенном переулке, большое вам спасибо». ржу не могу
@Sundae Пожалуйста, объясните protected and non-virtual part.
@Jos: Из статьи: «Короче говоря, у вас остается одна из двух ситуаций. Либо: а) вы хотите разрешить полиморфное удаление с помощью базового указателя, и в этом случае деструктор должен быть виртуальным и общедоступным; или б) вы этого не сделаете, и в этом случае деструктор должен быть невиртуальным и защищенным, последнее - для предотвращения нежелательного использования ".
Чтобы прояснить, похоже, что виртуальный деструктор необходим, если тип будет передан в delete, когда он был выделен как производный тип. Производному типу нет нужен виртуальный деструктор, если к нему не применимо то же самое. Таким образом, класс final никогда не нуждается в виртуальном деструкторе, даже если он является производным от класса, у которого он есть.
@fuzzyTew Если класс D является производным от класса B с виртуальным деструктором, то D также имеет виртуальный деструктор, даже если он не объявляет его явно. В большинстве случаев виртуальный деструктор необходимо объявлять только классу на вершине иерархии, а не остальной части иерархии.
@Luc Touraille: да, ваш вывод решает мою проблему. Всегда можно создать экземпляр любого неабстрактного класса. Поэтому, чтобы предотвратить это «неопределенное поведение», любой неабстрактный класс всегда должен определяться с помощью виртуального деструктора. Но большинство классов, с которыми я работал, вообще не имеют виртуального деструктора. Так что, возможно, у одного из их базовых классов он уже есть!
Объявите деструкторы виртуальными в полиморфных базовых классах. Это пункт 7 в Эффективный C++ Скотта Мейерса. Далее Мейерс резюмирует, что если класс имеет виртуальную функцию любой, он должен иметь виртуальный деструктор, и что классы, не предназначенные для использования в качестве базовых классов или не предназначенные для использования полиморфно, должны объявлять виртуальные деструкторы в нет.
+ «Если у класса есть какая-либо виртуальная функция, он должен иметь виртуальный деструктор, и классы, не предназначенные для использования в качестве базовых или не предназначенные для использования полиморфно, не должны объявлять виртуальные деструкторы.»: Есть ли случаи, когда имеет смысл нарушить это правило? Если нет, имеет ли смысл заставить компилятор проверять это условие и выдавать ошибку, если оно не выполнено?
@Giorgio Я не знаю никаких исключений из правил. Но я бы не стал оценивать себя как эксперта по C++, поэтому вы можете опубликовать это как отдельный вопрос. Предупреждение компилятора (или предупреждение от инструмента статического анализа) имеет для меня смысл.
Классы могут быть спроектированы так, чтобы их нельзя было удалить с помощью указателя определенного типа, но при этом они по-прежнему имеют виртуальные функции - типичным примером является интерфейс обратного вызова. Его реализация не удаляется с помощью указателя интерфейса обратного вызова, поскольку он используется только для подписки, но он имеет виртуальные функции.
@dascandy Exactly - это или все многие другие ситуации, когда мы используем полиморфное поведение, но не выполняем управление хранилищем с помощью указателей - например, поддержание объектов с автоматической или статической продолжительностью, с указателями, используемыми только в качестве маршрутов наблюдения. Нет необходимости / цели в реализации виртуального деструктора в любых таких случаях. Поскольку здесь мы просто цитируем людей, я предпочитаю Саттера сверху: «Рекомендация №4: деструктор базового класса должен быть либо общедоступным и виртуальным, либо защищенным и невиртуальным». Последнее гарантирует, что любой, кто случайно пытается удалить с помощью базового указателя, показывает ошибку своего пути.
@Giorgio На самом деле есть уловка, которую можно использовать и избежать виртуального вызова деструктора: привязать через константную ссылку производный объект к базе, например const Base& = make_Derived();. В этом случае будет вызываться деструктор prvalue Derived, даже если он не виртуальный, поэтому можно сэкономить накладные расходы, вносимые vtables / vpointers. Конечно, возможности довольно ограничены. Андрей Александреску упомянул об этом в своей книге Современный дизайн C++.
@Giorgio есть предупреждение в gcc только для этой ситуации. dascandy, они изначально не смогли принять вашу точку зрения как задокументировано здесь.
Почему все работает, если нет виртуального метода без полиморфного деструктора? РЕДАКТИРОВАТЬ ответ: нет, но в этом случае вам никогда не понадобится писать Base *b = new Derived1, который является источником всех проблем, а скорее Derived1 *b = new Derived1. Кроме того, без new не может быть проблем, поскольку компилятор всегда знает настоящий тип того, что выходит за рамки.
Также имейте в виду, что удаление указателя базового класса при отсутствии виртуального деструктора приведет к неопределенное поведение. То, что я узнал совсем недавно:
Как должно вести себя переопределение удаления в C++?
Я много лет использую C++ и до сих пор могу повеситься.
Я посмотрел на ваш вопрос и увидел, что вы объявили базовый деструктор виртуальным. Так остается ли в силе выражение «удаление указателя базового класса при отсутствии виртуального деструктора к неопределенному поведению» в отношении этого вашего вопроса? Поскольку в этом вопросе, когда вы вызывали delete, производный класс (созданный его оператором new) сначала проверяется на совместимую версию. Поскольку он нашел там одного, его назвали. Итак, не думаете ли вы, что было бы лучше сказать, что «удаление указателя базового класса при отсутствии деструктора приведет к неопределенному поведению»?
Это почти то же самое. Конструктор по умолчанию не виртуальный.
Мне нравится думать об интерфейсах и реализациях интерфейсов. В C++ интерфейс Speech - это чистый виртуальный класс. Деструктор является частью интерфейса и должен быть реализован. Поэтому деструктор должен быть чисто виртуальным. А как насчет конструктора? Конструктор на самом деле не является частью интерфейса, потому что объект всегда создается явно.
Это другой взгляд на тот же вопрос. Если мы будем думать в терминах интерфейсов, а не базового класса и производного класса, то естественный вывод: если он является частью интерфейса, то сделайте его виртуальным. Если это не так.
+1 за указание сходства объектно-ориентированной концепции интерфейс и C++ чистый виртуальный класс. Что касается деструктор, как ожидается, будет реализован: в этом часто нет необходимости. Если класс не управляет ресурсом, таким как необработанная динамически выделяемая память (например, не через интеллектуальный указатель), дескриптор файла или дескриптор базы данных, использование деструктора по умолчанию, созданного компилятором, нормально в производных классах. И обратите внимание, что если деструктор (или любая функция) объявлен virtual в базовом классе, он автоматически становится virtual в производном классе, даже если он не объявлен так.
При этом упускается важная деталь, заключающаяся в том, что деструктор является частью интерфейса не обязательно. Можно легко запрограммировать классы, которые имеют полиморфные функции, но которые вызывающий не управляет / не может удалить. Тогда у виртуального деструктора нет цели. Конечно, чтобы гарантировать это, невиртуальный деструктор - вероятно, по умолчанию - должен быть закрытым. Если бы мне пришлось угадывать, я бы сказал, что такие классы чаще используются внутри проектов, но это не делает их менее актуальными в качестве примера / нюанса во всем этом.
Виртуальный конструктор невозможен, но возможен виртуальный деструктор. Давайте поэкспериментируем .......
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
Приведенный выше код выводит следующее:
Base Constructor Called
Derived constructor called
Base Destructor called
Создание производного объекта следует правилу построения, но когда мы удаляем указатель «b» (базовый указатель), мы обнаружили, что вызывается только базовый деструктор. Но этого не должно быть. Чтобы сделать что-то подходящее, мы должны сделать базовый деструктор виртуальным. Теперь посмотрим, что происходит в следующем:
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
virtual ~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
Вывод изменился следующим образом:
Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called
Таким образом, уничтожение базового указателя (который занимает выделение для производного объекта!) Следует правилу уничтожения, то есть сначала Derived, а затем Base. С другой стороны, нет ничего лучше виртуального конструктора.
«виртуальный конструктор невозможен» означает, что вам не нужно писать виртуальный конструктор самостоятельно. Построение производного объекта должно следовать цепочке построения от производного к основному. Таким образом, вам не нужно писать виртуальное ключевое слово для вашего конструктора. Спасибо
@Murkantilism, "виртуальные конструкторы не могут быть сделаны" действительно правда. Конструктор нельзя пометить как виртуальный.
@cmeub, но есть идиома, чтобы добиться от виртуального конструктора того, что вы хотели бы. См. parashift.com/c++-faq-lite/virtual-ctors.html
@TunvirRahmanTusher не могли бы вы объяснить, почему называется Base Destructor ??
@rimiro Автоматически на C++. Вы можете перейти по ссылке stackoverflow.com/questions/677620/…
Что такое виртуальный деструктор или как использовать виртуальный деструктор
Деструктор класса - это функция с тем же именем, что и у класса, предшествующего ~, которая перераспределяет память, выделенную классом. Зачем нужен виртуальный деструктор
См. Следующий пример с некоторыми виртуальными функциями
В образце также рассказывается, как преобразовать букву в верхнюю или нижнюю.
#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
~convertch(){};
};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
Letter[i] = Letter[i] + 32;
return Letter;
}
private:
char *Letter;
bool tolower;
};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
Letter[i] = Letter[i] - 32;
return Letter;
}
virtual ~MakeUpper()
{
cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};
int _tmain(int argc, _TCHAR* argv[])
{
convertch *makeupper = new MakeUpper("hai");
cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";
delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" ";
delete makelower;
return 0;
}
Из приведенного выше примера видно, что деструктор для классов MakeUpper и MakeLower не вызывается.
См. Следующий пример с виртуальным деструктором
#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor
};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;
}
return Letter;
}
private:
char *Letter;
bool tolower;
};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};
int _tmain(int argc, _TCHAR* argv[])
{
convertch *makeupper = new MakeUpper("hai");
cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";
delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";
delete makelower;
return 0;
}
Виртуальный деструктор будет явно вызывать наиболее производный деструктор времени выполнения класса, чтобы он мог правильно очистить объект.
Или перейдите по ссылке
Я думаю, что суть этого вопроса касается виртуальных методов и полиморфизма, а не конкретно деструктора. Вот более наглядный пример:
class A
{
public:
A() {}
virtual void foo()
{
cout << "This is A." << endl;
}
};
class B : public A
{
public:
B() {}
void foo()
{
cout << "This is B." << endl;
}
};
int main(int argc, char* argv[])
{
A *a = new B();
a->foo();
if (a != NULL)
delete a;
return 0;
}
Распечатаю:
This is B.
Без virtual он распечатает:
This is A.
И теперь вы должны понимать, когда использовать виртуальные деструкторы.
Нет, это только воспроизводит самые основы виртуальных функций, полностью игнорируя нюансы того, когда и почему деструктор должен быть одним - что не так интуитивно понятно, поэтому OP задал вопрос. (Кроме того, почему здесь ненужное динамическое размещение? Просто сделайте B b{}; A& a{b}; a.foo();. Проверка NULL - который должен быть nullptr - перед deleteing - с неправильным отступом - не требуется: delete nullptr; определен как бездействующий. Во всяком случае, вы должны проверить это перед вызовом ->foo(), поскольку в противном случае может возникнуть неопределенное поведение, если new каким-то образом выйдет из строя.)
Вызывать delete по указателю NULL безопасно (т.е. вам не нужна защита if (a != NULL)).
@SaileshD Да, я знаю. Это то, что я сказал в мой комментарий
когда вам нужно вызвать деструктор производного класса из базового класса. вам необходимо объявить деструктор виртуального базового класса в базовом классе.
Любой класс, который унаследован публично, полиморфный или нет, должен иметь виртуальный деструктор. Другими словами, если на него может указывать указатель базового класса, его базовый класс должен иметь виртуальный деструктор.
Если виртуальный, вызывается деструктор производного класса, а затем конструктор базового класса. Если не виртуальный, вызывается только деструктор базового класса.
Я бы сказал, что это необходимо только «если на него может указывать указатель базового класса». а также может быть удален публично. Но, думаю, не помешает выработать привычку добавлять виртуальные дторы на случай, если они могут понадобиться позже.
Ключевое слово Virtual для деструктора необходимо, когда вы хотите, чтобы разные деструкторы следовали правильному порядку, пока объекты удаляются с помощью указателя базового класса. Например:
Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ;
Если деструктор базового класса виртуальный, объекты будут уничтожены в определенном порядке (сначала производный объект, затем базовый). Если ваш деструктор базового класса НЕ виртуальный, то будет удален только объект базового класса (потому что указатель имеет базовый класс «Base * myObj»). Таким образом, будет утечка памяти для производного объекта.
struct Base {
virtual void f() {}
virtual ~Base() {}
};
struct Derived : Base {
void f() override {}
~Derived() override {}
};
Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived
Вызов виртуального деструктора ничем не отличается от вызова любой другой виртуальной функции.
Для base->f() вызов будет отправлен на Derived::f(), и то же самое для base->~Base() - его функции переопределения - будет вызываться Derived::~Derived().
То же самое происходит, когда деструктор вызывается косвенно, например. delete base;. Оператор delete вызовет base->~Base(), который будет отправлен на Derived::~Derived().
Если вы не собираетесь удалять объект через указатель на его базовый класс - то виртуальный деструктор не нужен. Просто сделайте его protected, чтобы он случайно не назывался:
// library.hpp
struct Base {
virtual void f() = 0;
protected:
~Base() = default;
};
void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.
//-------------------
// application.cpp
struct Derived : Base {
void f() override { ... }
};
int main() {
Derived derived;
CallsF(derived);
// No need for virtual destructor here as well.
}
Нужно ли явно объявлять ~Derived() во всех производных классах, даже если это просто ~Derived() = default? Или это подразумевается языком (что позволяет безопасно опустить)?
@Wallacoloo нет, объявляйте это только тогда, когда это необходимо. Например. поместить в раздел protected, или сделать его виртуальным с помощью override.
Чтобы быть простым, Виртуальный деструктор предназначен для уничтожения ресурсов в правильном порядке, когда вы удаляете указатель базового класса, указывающий на объект производного класса.
#include<iostream>
using namespace std;
class B{
public:
B(){
cout<<"B()\n";
}
virtual ~B(){
cout<<"~B()\n";
}
};
class D: public B{
public:
D(){
cout<<"D()\n";
}
~D(){
cout<<"~D()\n";
}
};
int main(){
B *b = new D();
delete b;
return 0;
}
OUTPUT:
B()
D()
~D()
~B()
==============
If you don't give ~B() as virtual. then output would be
B()
D()
~B()
where destruction of ~D() is not done which leads to leak
Отсутствие базового виртуального деструктора и вызов delete по базовому указателю приводит к неопределенному поведению.
@JamesAdkison, почему это приводит к неопределенному поведению ??
@rimiro Это то, что говорит стандарт. У меня нет копии, но ссылка приведет вас к комментарию, где кто-то ссылается на местоположение в стандарте.
@rimiro "Если удаление, следовательно, может быть выполнено полиморфно через интерфейс базового класса, тогда оно должно вести себя виртуально и должно быть виртуальным. Действительно, язык требует этого - если вы удалите полиморфно без виртуального деструктора, вы вызовете ужасный призрак «неопределенное поведение» - призрак, которого я лично не хотел бы встречать даже в умеренно хорошо освещенном переулке, большое вам спасибо ». (gotw.ca/publications/mill18.htm) - Сатер с травами
Деструкторы виртуального базового класса - это «лучшая практика» - вы всегда должны использовать их, чтобы избежать (трудно обнаруживаемых) утечек памяти. Используя их, вы можете быть уверены, что все деструкторы в цепочке наследования ваших классов вызываются (в правильном порядке). При наследовании от базового класса с помощью виртуального деструктора деструктор наследующего класса также автоматически становится виртуальным (поэтому вам не нужно повторно вводить «виртуальный» в объявлении деструктора наследующего класса).
Я подумал, что было бы полезно обсудить поведение «undefined» или, по крайней мере, поведение undefined «сбой», которое может произойти при удалении через базовый класс (/ struct) без виртуального деструктора или, точнее, без vtable. В приведенном ниже коде перечислено несколько простых структур (то же самое верно и для классов).
#include <iostream>
using namespace std;
struct a
{
~a() {}
unsigned long long i;
};
struct b : a
{
~b() {}
unsigned long long j;
};
struct c : b
{
~c() {}
virtual void m3() {}
unsigned long long k;
};
struct d : c
{
~d() {}
virtual void m4() {}
unsigned long long l;
};
int main()
{
cout << "sizeof(a): " << sizeof(a) << endl;
cout << "sizeof(b): " << sizeof(b) << endl;
cout << "sizeof(c): " << sizeof(c) << endl;
cout << "sizeof(d): " << sizeof(d) << endl;
// No issue.
a* a1 = new a();
cout << "a1: " << a1 << endl;
delete a1;
// No issue.
b* b1 = new b();
cout << "b1: " << b1 << endl;
cout << "(a*) b1: " << (a*) b1 << endl;
delete b1;
// No issue.
c* c1 = new c();
cout << "c1: " << c1 << endl;
cout << "(b*) c1: " << (b*) c1 << endl;
cout << "(a*) c1: " << (a*) c1 << endl;
delete c1;
// No issue.
d* d1 = new d();
cout << "d1: " << d1 << endl;
cout << "(c*) d1: " << (c*) d1 << endl;
cout << "(b*) d1: " << (b*) d1 << endl;
cout << "(a*) d1: " << (a*) d1 << endl;
delete d1;
// Doesn't crash, but may not produce the results you want.
c1 = (c*) new d();
delete c1;
// Crashes due to passing an invalid address to the method which
// frees the memory.
d1 = new d();
b1 = (b*) d1;
cout << "d1: " << d1 << endl;
cout << "b1: " << b1 << endl;
delete b1;
/*
// This is similar to what's happening above in the "crash" case.
char* buf = new char[32];
cout << "buf: " << (void*) buf << endl;
buf += 8;
cout << "buf after adding 8: " << (void*) buf << endl;
delete buf;
*/
}
Я не предлагаю, нужны ли вам виртуальные деструкторы или нет, хотя я думаю, что в целом иметь их - это хорошая практика. Я просто указываю причину, по которой у вас может произойти сбой, если ваш базовый класс (/ struct) не имеет vtable, а ваш производный класс (/ struct) имеет, и вы удаляете объект через базовый класс (/ struct) указатель. В этом случае адрес, который вы передаете свободной подпрограмме кучи, недействителен и, следовательно, является причиной сбоя.
Если вы запустите приведенный выше код, вы четко увидите, когда возникнет проблема. Когда указатель this базового класса (/ struct) отличается от указателя this производного класса (/ struct), вы столкнетесь с этой проблемой. В приведенном выше примере структуры a и b не имеют vtables. в структурах c и d есть таблицы vtables. Таким образом, указатель a или b на экземпляр объекта c или d будет зафиксирован для учета vtable. Если вы передадите этот указатель a или b для удаления, он выйдет из строя из-за того, что адрес недействителен для бесплатной процедуры кучи.
Если вы планируете удалить производные экземпляры, которые имеют vtables из указателей базового класса, вам необходимо убедиться, что у базового класса есть vtable. Один из способов сделать это - добавить виртуальный деструктор, который в любом случае может понадобиться для правильной очистки ресурсов.
Если вы используете shared_ptr (только shared_ptr, а не unique_ptr), вам не обязательно иметь виртуальный деструктор базового класса:
#include <iostream>
#include <memory>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){ // not virtual
cout << "Base Destructor called\n";
}
};
class Derived: public Base
{
public:
Derived(){
cout << "Derived constructor called\n";
}
~Derived(){
cout << "Derived destructor called\n";
}
};
int main()
{
shared_ptr<Base> b(new Derived());
}
выход:
Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called
Хотя это возможно, я бы отговорил кого-либо от его использования. Накладные расходы виртуального деструктора незначительны, и это просто дает возможность ошибиться, особенно для менее опытного программиста, который этого не знает. Это маленькое ключевое слово virtual может спасти вас от многих мучений.
Основное определение virtual заключается в том, что он определяет, можно ли переопределить функцию-член класса в его производных классах.
D-tor класса вызывается в основном в конце области видимости, но возникает проблема, например, когда мы определяем экземпляр в куче (динамическое размещение), мы должны удалить его вручную.
Как только инструкция будет выполнена, вызывается деструктор базового класса, но не производного.
Практический пример - это когда в поле управления вам нужно манипулировать исполнительными механизмами, исполнительными механизмами.
В конце области, если деструктор одного из силовых элементов (Actuator) не вызывается, будут фатальные последствия.
#include <iostream>
class Mother{
public:
Mother(){
std::cout<<"Mother Ctor"<<std::endl;
}
virtual~Mother(){
std::cout<<"Mother D-tor"<<std::endl;
}
};
class Child: public Mother{
public:
Child(){
std::cout<<"Child C-tor"<<std::endl;
}
~Child(){
std::cout<<"Child D-tor"<<std::endl;
}
};
int main()
{
Mother *c = new Child();
delete c;
return 0;
}
Смотрите это: Виртуальный деструктор