去初始化成语期间是否有任何动态绑定
Is there any Dynamic Binding During Deinitialization idiom
又名:有没有"Calling Virtuals During Deinitialization"成语
我正在清理一些旧代码,需要修复在构造函数和析构函数中调用虚拟方法的情况。我不知道代码库,而且它很大。主要重写不是一个选项。
构造函数的修复很简单。我将虚拟调用移至静态 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")。我需要解决这类问题。
...
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
}
...
并在发现析构函数时清理它们的虚函数调用。
这非常棘手,因为在离开作用域时会自动调用析构函数,无论是通过正常流程,break
、continue
、return
还是 throw
。这也是为什么不能将参数传递给析构函数的原因。
直接的解决方案是从 Derived::~Derived
调用 Derived::DeInit
。这还有一个额外的好处,即仍然有 Derived
名成员可用。
另一种是创建自己的智能指针class,它在T::~T
之前调用T::DeInit
。为了防止这个被绕过,return 这个智能指针来自你的 Create
.
您不需要享受虚拟 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;
}
输出:
衍生的
基地
这是你想要的吗?
受 MSalter 的第二个想法启发的解决方案。
此解决方案仅需要更改 Base
class 和 Derived
classes 的实例化。无需对任何 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
classes,不知情的开发人员仍然可以创建一个新的 class 而忘记使其构造函数受保护。我想知道这是否可以通过在战略位置的某种断言来强制执行?
static_assert(std::is_constructible<Derived>::value, "Derived class is constructable");
顺便说一句:我最终选择重写代码。我认为它是可管理的,并且生成的代码会更简单(因此更好)。
又名:有没有"Calling Virtuals During Deinitialization"成语
我正在清理一些旧代码,需要修复在构造函数和析构函数中调用虚拟方法的情况。我不知道代码库,而且它很大。主要重写不是一个选项。
构造函数的修复很简单。我将虚拟调用移至静态 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")。我需要解决这类问题。
...
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
}
...
并在发现析构函数时清理它们的虚函数调用。
这非常棘手,因为在离开作用域时会自动调用析构函数,无论是通过正常流程,break
、continue
、return
还是 throw
。这也是为什么不能将参数传递给析构函数的原因。
直接的解决方案是从 Derived::~Derived
调用 Derived::DeInit
。这还有一个额外的好处,即仍然有 Derived
名成员可用。
另一种是创建自己的智能指针class,它在T::~T
之前调用T::DeInit
。为了防止这个被绕过,return 这个智能指针来自你的 Create
.
您不需要享受虚拟 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;
}
输出: 衍生的 基地
这是你想要的吗?
受 MSalter 的第二个想法启发的解决方案。
此解决方案仅需要更改 Base
class 和 Derived
classes 的实例化。无需对任何 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
classes,不知情的开发人员仍然可以创建一个新的 class 而忘记使其构造函数受保护。我想知道这是否可以通过在战略位置的某种断言来强制执行?
static_assert(std::is_constructible<Derived>::value, "Derived class is constructable");
顺便说一句:我最终选择重写代码。我认为它是可管理的,并且生成的代码会更简单(因此更好)。