Ака: Есть ли какая-то идиома «Вызов виртуалов во время деинициализации»
Я очищаю старый код и мне нужно исправить случаи, когда виртуальные методы вызываются в конструкторах и деструкторах. Я не знаю кодовую базу, и она огромна. Основная перезапись не вариант.
Исправление для конструкторов было простым. Я переместил виртуальные звонки на статический Create
шаблон и сделал все конструкторы защищены. Затем все, что мне нужно было сделать, это скомпилировать и изменить все местоположение, вызывающее ошибки, чтобы использовать Create
шаблон. Минимальный шанс для регрессий. Однако для деструкторов аналога этому нет.
Как бы вы решили это?
Пример кода
#include <iostream>
class Base
{
public:
virtual ~Base()
{
DeInit();
}
protected:
virtual void DeInit()
{
std::cout << "Base" << std::endl;
}
};
class Derived : public Base
{
protected:
virtual void DeInit() override
{
std::cout << "Derived" << std::endl;
Base::DeInit();
}
};
int main()
{
Derived d;
}
Этот код не вызывает Derived::DeInit
(печатает только «База»). Мне нужно исправить такие проблемы.
Это довольно сложно, так как деструкторы вызываются автоматически при выходе из областей, будь то при нормальном потоке, break
, continue
, return
или же throw
, Вот почему вы не можете передавать аргументы деструктору.
Простое решение состоит в том, чтобы позвонить Derived::DeInit
от Derived::~Derived
, Это имеет дополнительное преимущество по-прежнему Derived
члены доступны.
Другой — создать свой собственный класс интеллектуальных указателей, который вызывает T::DeInit
до T::~T
, Чтобы избежать этого, верните этот умный указатель из вашего Create
,
...
virtual Base::~Base()
{
Base::DeInit();
}
...
...
Derived::~Derived()
{
// de-initialization code
// do not call Derived::DeInit() here as otherwise Base::DeInit()
// will be called two times
}
...
и очистка вызовов виртуальных функций от деструкторов при их обнаружении.
Вам не нужно иметь виртуальное веселье DeInit.
#include <iostream>
class Base
{
public:
virtual ~Base()
{
DeInit(); //this calls Base version
}
protected:
void DeInit()
{
std::cout << "Base" << std::endl;
}
};
class Derived : public Base
{
public:
~Derived()
{
DeInit(); //this calls Derived version
}
protected:
void DeInit()
{
std::cout << "Derived" << std::endl;
}
};
int main()
{
Derived d;
}
Выход:
Производный
База
это то, что вы хотели?
Решение, вдохновленное второй идеей MSalters.
Это решение требует только изменений в Base
класс и к созданию Derived
классы. Никаких изменений не требуется Derived
реализация.
#include <iostream>
#include <memory>
class Base
{
private:
template <class T>
class WithAutoDeInit : public T
{
public:
virtual ~WithAutoDeInit() override
{
T::DeInit();
}
};
public:
template <class T>
static std::unique_ptr<typename std::enable_if<std::is_base_of<Base, T>::value, WithAutoDeInit<T>>::type> Create()
{
return std::make_unique<WithAutoDeInit<T>>();
}
virtual ~Base() = default;
protected:
virtual void DeInit()
{
std::cout << "Base" << std::endl;
}
};
class Derived : public Base
{
protected:
virtual void DeInit() override
{
std::cout << "Derived" << std::endl;
Base::DeInit();
}
};
int main()
{
Base::Create<Derived>();
}
Это не надежное решение. Вы все еще можете сделать примеры Derived
непосредственно. И если вы обновите все свои Derived
классы с защищенными конструкторами, которые не знающий разработчик может создать новый класс, забыв сделать свои конструкторы защищенными. Интересно, может ли это быть обеспечено каким-то утверждением в стратегическом месте?
static_assert(std::is_constructible<Derived>::value, "Derived class is constructable");
Кстати: я, наконец, решил переписать код. Я думаю, что это управляемо, и полученный код будет проще, потому что лучше.