Почему необходимо определение для чисто виртуального деструктора?
struct A {
virtual ~A() = 0;
};
struct B : A {};
// A::~A() {}
int main() {
B{};
}
Почему программа содержит ошибку линковки?
undefined reference to `A::~A()'
Почему можно и нужно определять чисто виртуальную функцию?
Ответы (4 шт):
Абстрактный базовый класс, это частичка порожденного класса. И он может иметь у себя указатель, конструктор, выделяющий память и деструктор, её освобождающий. И этот класс также имеет у себя и конструктор и деструктор.
Абстрактность заключается в запрещении реального создания объекта чисто данного класса. Но можно создавать объекты других классов, у которых он является базовым.
Виртуальность деструктора позволяет вызывать удаление объекта основываясь только по указателю на абстрактный класс.
# include <iostream>
class Base {
public :
Base ();
virtual ~Base ()=0;
private :
int * ip ;
} ;
Base::Base(){
std::cout<<"Base()"<<std::endl;
ip = new int[10];
}
Base::~Base(){
std::cout<<"~Base()"<<std::endl;
delete[]ip;
}
class Son : public Base {
public :
Son ();
virtual ~Son ();
} ;
Son::Son():Base(){
std::cout<<"Son()"<<std::endl;
}
Son::~Son(){
std::cout<<"~Son()"<<std::endl;
}
int main(){
Base * b = new Son;
// вызов виртуального деструктора + удаляется объект Son
delete b ;
// реальный объект абстрактного класса создавать нельзя
// Base * bp = new Base;
}
смотрим как создаются и уничтожаются объекты :
Base()
Son()
~Son()
~Base()
Пока деструктор не определен, не возможно удалять объекты, созданные на его базе. Виртуальный деструктор делает возможным удалять объекты, при обращении к родителю. Чисто виртуальный деструктор обычно не применяется, но не значит, что не может применяться.
зачем тогда разрешать конструкцию чисто виртуального деструктора, если он не может быть чисто виртуальным?
Ну а зачем запрещать? Можно придумать примеры, когда чисто виртуальный или защищенный деструктор может стать инструментом для решения конкретной задумки, также, как и удаленный или защищенный конструктор. Посмотрим на простом примере:
//file a.h
struct A {
//или наследник класса, имеющий такой деструктор
virtual ~A() = 0;
//много полезных функций, например
template<class Piece>
Piece* CreatePiece()
{
return new Piece;
}
};
#ifdef Core
A::~A() {};
#endif // Core
//file m.h
#define Core
#include "a.h"
//деструктор "А" определен
struct Builder : A { /*... */};
struct D {
//можно пользоваться строителем
Builder b;
};
//file X
#include "a.h"
//деструктор "А" не определен
struct My_locale : A {};
//нельзя пользоваться объектом класса
//не разрешен вызов деструктора базового класса
Не знаю имеет ли смысл отвечать на старые вопросы, но раз я сам разбирался, то вдруг мой ответ тоже поможет людям)
С чего же лучше начать объяснение...
Как мы знаем если у нас полиморфизм то деструктор должен быть объявлен виртуальным через ключевое слово virtual, что обеспечивает корректное освобождение памяти и вызов всех деструкторов в обратной иерархии создания объекта, думаю это вы должны понимать, если нет то можете найти почитать про наследование и полиморфизм.(можно тут)
Пример:
class Base
{
public:
Base() = default;
virtual ~Base();
}
Далее нужно знать про чисто виртуальные методы их мы помечаем = 0, что говорит что класс становится абстрактным и эти методы должны быть определены в классе наследнике(чтобы можно было создавать объекты, нужно определить все чисто вирт. методы)
Пример:
class Base
{
public:
Base() = default;
virtual ~Base();
void doSmth() = 0;
}
Но вот возникает интересная ситуация, например мы хотим создать базовый интерфейс и там не может быть никаких чисто виртуальных методов, максимум что там может быть это виртуальных метод для освобождения памяти, который уже определен.... и вот возникает загвоздка, нам нужно запретить создание этого класса -> значит он должен быть абстрактным -> должен быть хотя бы 1 чисто виртуальный метод -> ...?? а где взять этот метод, когда его там нету??
Вот тут и приходит на помощь чисто виртуальный деструктор, он делает класс абстрактным и решает все наши проблемы, НО он долен быть определен для корректной работы с удалением объекта.
Пойдем от обратного и не определим чисто вирт. деструктор, подумает что произойдет?
Мы получим ситуацию, что при удалении объекта он будет знать про все деструкторы даже если мы работаем через родительский указатель(т.к. он виртуальный) и начнет их вызывать, пусть даже они будут пустые и когда он дойдет до базового, если нету реализации что ему вызвать? Нужно понимать что когда у нас есть иерархия классов наследования и мы создаем объект, то как же как мы вызываем его конструктор(там мы может что-то проинициализировать), то мы так-же и вызываем его деструктор при уничтожении и чисто виртуальный деструктор нам говорит не про то, что это метод не нужно реализовывать, а только про то, что этот класс абстрактный и все!
Пример:
class IBase
{
protected:
IBase() = default;
virtual ~IBase() = 0;
virtual void release();
public:
IBase(const IBase&) = delete;
IBase& operator=(const IBase&) = delete;
struct Releaser
{
void operator()(IBase* p) const
{
p->release();
}
};
friend struct IBase::Releaser;
};
IBase::~IBase() = default;
void IBase::release()
{
delete this;
}
Если подвести итог, то надо понимать, что деструктор должен быть определен всегда, это нужно принять как факт, если вы его не пишите то он сам автоматически сгенерируется компилятором иногда это нужно указывать в явном виде, поэтому это можно запомнить как исключение, что хоть деструктор может быть чисто виртуальным, он все равно должен быть определен
Может возникнуть пару вопросов:
А надо ли делать чисто виртуальный деструктор, если у нас уже есть хотя бы один чисто виртуальный метод?
Нет, не нужно, т.к. класс уже станет абстрактным из-за метода.
Тогда нужно ли явно определять в наследнике деструктор, чтобы этот класс тоже не был абстрактным?
Нет, не нужно он по умолчанию просто будет =default;
Деструктор потомка автоматически вызывает деструктор родителя, поэтому деструктор родителя должен быть определен. Аналогично деструкторы всех полей.
Для сравнения, у чисто-виртуальной обычной функции (не деструктора) определение нужно только если ее вызывают вручную (часто - из переопределенной той же функции в потомке).
Искусственный пример:
struct Animal
{
virtual void SayHello() = 0;
};
void Animal::SayHello()
{
std::cout << "Hello!\n";
}
struct Dog : Animal
{
void SayHello() override
{
std::cout << "Dog says:\n";
Animal::SayHello();
}
};