У меня есть твердое понимание большинства теории OO, но единственное, что меня сильно смущает, это виртуальные деструкторы.
Я думал, что деструктор всегда вызывается независимо от того, что и для каждого объекта в цепочке.
Когда вы должны сделать их виртуальными и почему?
Виртуальные деструкторы полезны, когда вы можете удалить экземпляр производного класса через указатель на базовый класс:
class Base
{
// some virtual methods
};
class Derived : public Base
{
~Derived()
{
// Do some important cleanup
}
};
Здесь вы заметите, что я не объявлял деструктор Базы «виртуальным». Теперь давайте посмотрим на следующий фрагмент:
Base *b = new Derived();
// use b
delete b; // Here's the problem!
Поскольку деструктор Base не является virtual
, а b
- Base *
, указывающим на объектDerived
, delete b
имеет неопределенное поведение:
[В
delete b
], если статический тип объект, который будет удален, отличается от его динамического типа, статического тип должен быть базовым классом динамического типа объекта, который должен быть удаленный и статический тип должен иметь виртуальный деструктор или поведение не определено.
В большинстве реализаций вызов деструктору будет решаться как любой невиртуальный код, что означает, что деструктор базового класса будет вызываться, но не как производный класс, что приведет к утечке ресурсов.
Подводя итог, всегда делайте деструкторы базовых классов «виртуальными», когда ими нужно манипулировать полиморфно.
Если вы хотите предотвратить удаление экземпляра с помощью указателя базового класса, вы можете сделать деструктор базового класса защищенным и не виртуальным; таким образом, компилятор не позволит вам вызывать delete
на указателе базового класса.
Вы можете узнать больше о виртуальности и деструкторе виртуального базового класса в этой статье Херба Саттера.
Виртуальный конструктор невозможен, но возможен виртуальный деструктор. Давайте экспериментировать....
#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;
}
Вышеуказанный код выводит следующее:
& Лт;!- язык: lang-none - >
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;
}
Выход изменился следующим образом:
& Лт;!- язык: lang-none - >
Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called
Таким образом, уничтожение базового указателя (который принимает распределение на производном объекте!) следует правилу уничтожения, то есть сначала Производный, затем База. С другой стороны, нет ничего лучше виртуального конструктора.
Объявите деструкторы виртуальными в полиморфных базовых классах. Это пункт 7 в книге Скотта Мейерса Effective C ++. Далее Мейерс резюмирует, что если класс имеет любую виртуальную функцию, он должен иметь виртуальный деструктор, и что классы, не предназначенные для базовых классов или не предназначенные для полиморфного использования, не должны * объявлять виртуальные деструкторы.
Также имейте в виду, что удаление указателя базового класса, когда виртуального деструктора нет, приведет к неопределенному поведению . То, что я узнал совсем недавно:
https://stackoverflow.com/questions/408196/how-show-overriding-delete-in-c-behave
Я использую C ++ в течение многих лет, и мне все еще удается повеситься.
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- > ~ Base ()
, который будет отправлен в Derived :: ~ Derived ()
.
Если вы не собираетесь удалять объект через указатель на его базовый класс - тогда нет необходимости иметь виртуальный деструктор. Просто сделайте его «защищенным», чтобы его не вызывали случайно
// 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.
}
Мне нравится думать об интерфейсах и реализациях интерфейсов. В C ++ разговорный интерфейс - это чистый виртуальный класс. Деструктор является частью интерфейса и ожидается его реализация. Поэтому деструктор должен быть чисто виртуальным. Как насчет конструктора? Конструктор фактически не является частью интерфейса, потому что объект всегда создается явно.
Быть простым Виртуальный деструктор предназначен для уничтожения ресурсов в правильном порядке при удалении указателя базового класса, указывающего на производный объект класса.
#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
Виртуальное ключевое слово для деструктора необходимо, когда вы хотите, чтобы разные деструкторы следовали правильному порядку, пока объекты удаляются через указатель базового класса. например:
Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ;
Если ваш производный класс деструктор является виртуальным, то объекты будут уничтожены в порядке (сначала производный объект, затем база). Если ваш производный деструктор класса НЕ является виртуальным, то удаляется только объект базового класса (потому что указатель относится к базовому классу «Base * myObj»). Таким образом, будет утечка памяти для производного объекта.
Что такое виртуальный деструктор или как использовать виртуальный деструктор
Разрушитель классов - это функция с тем же именем класса, предшествующего ~, которая перераспределяет память, выделенную классом. Зачем нам нужен виртуальный деструктор
Смотрите следующий образец с некоторыми виртуальными функциями
Образец также показывает, как вы можете преобразовать букву в верхнюю или нижнюю
#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;
}
Виртуальный деструктор будет явно вызывать наиболее производный деструктор времени выполнения класса, чтобы он мог очистить объект надлежащим образом.
Или посетите ссылку
Разрушители виртуального базового класса являются «лучшей практикой» - вы всегда должны использовать их, чтобы избежать (трудно обнаружить) утечек памяти. Используя их, вы можете быть уверены, что все деструкторы в цепочке наследования ваших классов называются (в правильном порядке). Наследование из базового класса с использованием виртуального деструктора делает деструктор унаследованного класса автоматически виртуальным (поэтому вам не нужно перепечатывать «виртуальный» в декларации деструктора унаследованного класса).
Если вы используете shared_ptr
(только shared_ptr, а не unique_ptr), вам не нужно иметь виртуальный деструктор базового класса:
#include < iostream >
#include < память >
используя пространство имен std ;
класс База
{
общественности:
Base () {
cout < < "Базовый конструктор называется \ n" ;
}
~ Base () {// не виртуальный
cout < < "Базовый деструктор называется \ n" ;
}
};
класс Производный: публичная база
{
общественности:
Производный () {
cout < < "Производный конструктор называется \ n" ;
}
~ Производный () {
cout < < "Производный деструктор называется \ n" ;
}
};
int main ()
{
shared_ptr < Base > b (новый производный ()) ;
}
вывод:
& Лт;!- язык: lang-none - >
Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called
Я подумал, что было бы полезно обсудить «неопределенное» поведение или, по крайней мере, неопределенное поведение «сбой», которое может возникнуть при удалении через базовый класс (/ структуру) без виртуального деструктора или, точнее, без 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) указатель. В этом случае адрес, который вы передаете в бесплатную рутину кучи, недействителен и, следовательно, является причиной сбоя.
Если вы запустите приведенный выше код, вы четко увидите, когда возникнет проблема. Когда этот указатель базового класса (/ struct) отличается от этого указателя производного класса (/ struct), вы столкнетесь с этой проблемой. В приведенном выше примере у структур a и b нет фронтонов. У структур c и d есть vtables. Таким образом, указатель a или b на экземпляр объекта c или d будет исправлен для учета vtable. Если вы передадите этот указатель a или b для удаления, он рухнет из-за недействительности адреса в бесплатной программе кучи.
Если вы планируете удалять производные экземпляры, которые имеют vtables из указателей базового класса, вам необходимо убедиться, что базовый класс имеет vtable. Один из способов сделать это - добавить виртуальный деструктор, который вы можете в любом случае захотеть правильно очистить ресурсы.
Я думаю, что суть этого вопроса касается виртуальных методов и полиморфизма, а не деструктора. Вот более четкий пример:
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.
Без «виртуального» он распечатает:
This is A.
И теперь вы должны понимать, когда использовать виртуальные деструкторы.
когда вам нужно вызвать производный класс деструктора из базового класса. Вам нужно объявить виртуальный деструктор базового класса в базовом классе.
Любой класс, который наследуется публично, полиморфно или нет, должен иметь виртуальный деструктор. Другими словами, если на него может указывать указатель базового класса, его базовый класс должен иметь виртуальный деструктор.
Если виртуальный, производный деструктор класса вызывается, то конструктор базового класса. Если не виртуальный, вызывается только деструктор базового класса.