【问题标题】:Calling virtual function from a class destructor in C++从 C++ 中的类析构函数调用虚函数
【发布时间】:2021-05-18 16:21:30
【问题描述】:
在构造函数或析构函数中调用虚函数的问题已经在许多其他问题和资源中讨论过,但我想澄清一些关于它的东西,我仍然觉得很缺失。
例如,在 Scott Meyers, Effective C++, Item 9 中有一个完整的解释。但最后的评论是
在构造或销毁期间不要调用虚函数,因为这样的调用永远不会转到比当前执行的构造函数或析构函数更派生的类
那么我试图理解从构造函数或析构函数调用虚函数在什么意义上是不安全的。
- 由于虚拟表状态在析构函数中是“不确定的”,我可能会得到意外的运行时行为,这是否不安全?
- 或者,从某种意义上说它是不安全的,因为我可能会得到与我预期不同的行为,所以它是不可维护的?
谢谢
【问题讨论】:
标签:
c++
polymorphism
destructor
【解决方案1】:
从某种意义上说,我可能会遇到意外的运行时行为是否不安全...
意外行为正是它不安全的意义。程序的读者或作者可能期望调用虚函数会调用最派生的覆盖。这个假设在析构函数中不成立(在构造函数中也不成立),这可能是出乎意料的。
...因为虚拟表状态在析构函数中是“不确定的”?
这种行为没有任何“不确定性”。
或者,从某种意义上说它是不安全的,因为我可能会得到与我预期不同的行为,所以它是不可维护的?
意外行为通常不同于人们预期的行为。所以,是的。
【解决方案2】:
由于虚拟表状态在析构函数中是“不确定的”,我可能会得到意外的运行时行为,这是否不安全?
C++ 标准没有讨论 vtable 实现。它讨论了行为。并且行为是调用被静态解析(好像)。
就实现而言,那些通常“还原”vptr,使其指向当前正在析构的类的vtable,在析构函数执行的早期。
或者,从某种意义上说它是不安全的,因为我可能会得到与我预期不同的行为,所以它是不可维护的?
取决于您的期望。如果你知道调用是如何解决的,你会得到你所期望的
class Thing {
public:
virtual ~Thing() { frombulate(); }
void frombulate() const { do_frombulation(); }
private:
virtual void do_frombulation() const = 0;
};
inline void Thing::do_frombulation() const {}
就像我说的,你可能知道会发生什么行为,所以即使do_frombulation 是纯虚拟的,你也提供了一个实现。
如果不知道,您可能忽略了空定义。在这种情况下,您的程序将表现出未定义的行为(可能会崩溃,因为Thing 的 vtable 中的该条目不会被有效地址填充)并且您可能会对此感到惊讶。如果您在构造函数/析构函数体中直接调用未实现的纯虚拟do_frombulation,编译器可能会通知您,但它无法检查所有执行路径。