简体   繁体   中英

Is there any Dynamic Binding During Deinitialization idiom

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.

Working example code

...
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>();
}

Working example code

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM