Но это только вершина айсберга... оказывается, всем интерфейсам нужно добавить виртуальные деструкторы, иначе велик шанс, что деструктор реализации вызван не будет.
Вот почему.
Смотрим на обычный интерфейс и его реализацию:
class IFile
{
public:
virtual void Process() = 0;
};
class FileImplementation : public IFile
{
public:
FileImplementation()
{
// тут открываем файл
}
virtual ~FileImplementation()
{
// а тут закрываем файл
}
virtual void Process()
{
// какие-то действия
}
};
Что мы видим? Стандартная имплементация в стиле RAII.
Создаем экземпляр имплементирующего класса и проходимся отладчиком:
{
std::auto_ptr<FileImplementation> file(new FileImplementation());
file->Process();
}
При выходе из области видимости деструктор вызовется и файл закроется.
А теперь попробуем воспроизвести Dependency Injection:
{
std::auto_ptr<IFile> file(new FileImplementation());
file->Process();
}
И видим, что деструктор FileImplementation не будет вызван!
Объяснение простое. Интеллектуальный указатель будет удалять экземпляр IFile, который ничего не знает о своей реализации, кроме виртуальных методов. И про деструктор у имплементации он тоже ничего не знает, даже не смотря на то, что он – виртуальный. Причина: он становится виртуальным уже НИЖЕ по архитектуре.
В лучшем случае компилятор выкинет исключение. В худшем – промолчит, и баг останется жить.
Я борюсь с этим через объявление тривиального публичного виртуального конструктора в интерфейсе. Чисто виртуальный деструктор не годится, а прятать его в protected смысла нет. Да и нехорошо в интерфейсном классе объявлять protected, пусть даже деструктор.
class IFile
{
public:
virtual void Process() = 0;
virtual ~IFile() {};
};
Мораль сей басни такова. Добавьте во все классы виртуальный деструктор, хотя бы тривиальный. И будут они жить долго и счастливо...
Читать дальше...