【问题标题】:Is there any Dynamic Binding During Deinitialization idiom在去初始化习语期间是否有任何动态绑定
【发布时间】:2015-07-30 08:09:58
【问题描述】:

又名:是否有任何“取消初始化期间调用虚拟”的成语

我正在清理一些旧代码,并且需要修复在构造函数和析构函数中调用虚拟方法的情况。我不知道代码库,它很大。主要重写不是一个选项。

构造函数的修复很简单。我将虚拟调用移至静态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(仅打印“Base”)。我需要解决这类问题。

Working example code

【问题讨论】:

  • 你需要解决什么问题?虚拟析构函数已经按预期工作。
  • @KerrekSB:OP 希望避免在构造函数/析构函数中进行虚调用,因为调用虚方法的行为与在其他上下文中不同。

标签: c++ dynamic-binding


【解决方案1】:

这很棘手,因为在离开作用域时会自动调用析构函数,无论是通过正常流程breakcontinuereturn 还是throw。这也是你不能将参数传递给析构函数的原因。

直接的解决方案是从Derived::~Derived 调用Derived::DeInit。这还有一个额外的好处,那就是仍然有 Derived 成员可用。

另一个是创建你自己的智能指针类,它在T::~T之前调用T::DeInit。为防止被绕过,请从您的 Create 返回此智能指针。

【讨论】:

    【解决方案2】:
    ...
    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?顺便说一句,这通常是正确的选择。
    • @Msalters - 它归结为作者可以和不能触摸的内容以及他是否可以有效(=自动)从析构函数中完成“内联”方法调用
    【解决方案3】:

    您无需享受虚拟的 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;
        }
    

    输出: 衍生的 基地

    这是你想要的吗?

    【讨论】:

    • 如果您可以(敢于)更改 Derived,这是一个很好的解决方案。如果是这种情况,我什至会杀死DeInit(和Init)方法并采用正确的RAII。但是,如果代码库很大并且Base 是一个公共基类,那么Derived 类的数量可能会很大。可能有数百个或更多。在没有适当分析和测试的情况下对所有这些进行更改可能是个坏主意。
    【解决方案4】:

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

    Working example code

    这不是一个可靠的解决方案。您仍然可以直接创建 Derived 的实例。而且,如果您使用受保护的构造函数更新所有Derived 类,则不知情的开发人员仍然可以创建一个新类而忘记保护其构造函数。我想知道这是否可以通过在战略位置的某种断言来强制执行?

    static_assert(std::is_constructible&lt;Derived&gt;::value, "Derived class is constructable");

    顺便说一句:我最终选择重写代码。我认为它是可管理的,并且生成的代码会更简单(因此更好)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-01
      • 2015-12-09
      • 2022-11-20
      • 2012-05-29
      • 2020-07-18
      • 1970-01-01
      • 1970-01-01
      • 2018-09-08
      相关资源
      最近更新 更多