Aka: Is there any "Calling Virtuals During Deinitialization" idiom
I am cleaning up some old code and need to fix cases where virtual methods are called in constructors and destructors. I don't know the code base and it is huge. Major rewrite is not an option.
The fix for constructors was simple. I moved the virtual calls to a static Create
template and made all the constructors protected. Then all I needed to do was to compile and change all location causing errors to use the Create
template. Minimal chance for regressions. However there is no analog to this for destructors.
How would you solve this?
Example code
#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;
}
This code does not call Derived::DeInit
(only prints "Base"). I need to fix this kind of issues.
...
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
}
...
and cleanup of virtual function calls from destructors when spotting them.
This is quite tricky, as destructors are called automatically on leaving scopes, whether by normal flow, break
, continue
, return
or throw
. That's also why you can't pass arguments to a destructor.
The straightforward solution is to call Derived::DeInit
from Derived::~Derived
. This has the additional benefit of still having Derived
members available.
Another is to create your own smart pointer class, which calls T::DeInit
before T::~T
. To prevent this from being bypassed, return this smart pointer from your Create
.
you dont need to have a virtual DeInit fun.
#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;
}
Output: Derived Base
is this what you wanted?
A solution inspired by MSalters second idea.
This solution only requires changes to Base
class and to instantiation of Derived
classes. No changes needed to any Derived
implementation.
#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>();
}
This is not a robust solution. You can still make instances of Derived
directly. And if you update all your Derived
classes with protected constructors an unknowing developer could still create a new class forgetting to make its constructors protected. I wonder if this could be enforced by some kind of assert in a strategic location?
static_assert(std::is_constructible<Derived>::value, "Derived class is constructable");
Btw: I finally choose to rewite the code. It is managable I think, and the resulting code will off cause be simpler (hence better).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.