19

C++ 标准规定禁止从构造函数或析构函数调用纯虚函数。这是什么原因?为什么标准要设置这样的限制?

4

6 回答 6

23

在运行类析构函数时,所有子类析构函数都已运行。调用子类定义的虚方法是无效的,其析构函数已经运行。

在构造函数中调用虚方法也存在类似的限制。您不能为其构造函数尚未运行的子类调用虚方法。

于 2011-12-28T04:39:47.660 回答
5

这与你在浇筑地基或拆除地基时不能住在房子里的原因是一样的。在构造函数完成之前,对象只是部分构造。并且一旦析构函数启动,对象就会被部分销毁。纯虚函数只能在处于正常状态的对象上调用,否则可能不存在确定调用哪个函数实现所需的结构。

于 2011-12-28T04:39:33.427 回答
4

C++ 标准规定,禁止从构造函数或析构函数调用纯虚函数。这是什么原因?为什么标准要设置这样的限制?

来自公认的旧 C++ 标准草案,但我将得出的相关区别仍然相关:

10.4-6 可以从抽象类的构造函数(或析构函数)调用成员函数;对于从这样的构造函数(或析构函数)创建(或销毁)的对象,直接或间接地对纯虚函数进行虚拟调用(class.virtual)的效果是未定义的。

这与您所断言的内容略有不同,因为分号前短语的上下文与后置词隐含相关。改写:

undefined behaviour happens when an abstract class's constructor or destructor calls one of its own member functions that is (still) pure virtual.

我包括限定符“(仍然)”,因为在类层次结构中的某个点上给了纯虚函数定义 - 并且不再是“纯”的。这有点奇怪,但考虑一下标准的这一部分:

如果一个类至少有一个纯虚函数,那么它就是抽象的。[注意:这样的功能可能会被继承:见下文。]

显然,基类中具有纯虚函数定义的派生类本身不一定是抽象的。只有当“纯粹性”的含义不是普遍适用于虚函数本身,而是适用于它保持纯粹的类层次结构的级别时,上述陈述才能成立。

结果:如果函数 - 从层次结构中调用构造函数/析构函数的角度来看 - 已经定义,则可以以明确定义的行为调用它。

了解了标准中未定义的内容后,我们可以回到您的问题:“这是什么原因?标准为什么要设置这样的限制?”

原因的关键在于,必须在派生类构造函数开始之前完全构造基类,因为派生类的构造可能在基对象上运行。类似地,基析构函数必须在派生析构函数之后运行,因为后者可能仍希望访问基对象。鉴于这种必要的顺序,编译器无法安全地分派给派生类的虚函数,因为派生类的构造函数尚未运行以建立派生类成员状态,或者派生类的析构函数已经为附加数据调用析构函数成员和对象状态不再保证可用。

对于非纯虚函数,编译器可以并且确实依赖于调用该函数的最专门定义,该函数已知为层次结构中已经构造但尚未销毁的最派生类。但是根据定义,纯虚函数是那些尚未指定实现的函数,并且在函数是纯函数的类层次结构中,不存在可调用的实现。

虚拟分派机制的典型实现可以通过在基类的虚拟分派表中有一个指针来表示这一点,该基类包含设置为 0、未初始化或指向某个“引发警报”函数的纯虚函数。随着派生类构造函数的连续层启动,指向虚拟调度表的指针将被它们自己的 VDT 的地址覆盖。那些覆盖实现的派生类将指向它们自己的函数定义,这将成为任何本身不指定新实现的派生类的默认值。当派生类析构函数完成时,这个关键的指向 VDT 成员的隐式指针将向后移动通过相同的 VDT 列表,确保对类层次结构中未破坏层上的函数进行任何虚拟调用。但是,当定义了虚函数的第一个类的析构函数运行时,未来的 VDT 将再次缺少对实际实现的任何引用。

于 2011-12-28T10:39:37.463 回答
2

回想一下,从构造函数/析构函数调用“非纯”虚函数会忽略函数是虚函数的事实,并且总是在您的类中调用实现,而不是在正在构造的派生类中调用。这就是为什么你不能从构造函数或析构函数调用纯虚函数:就他们而言,你的纯虚函数没有实现。

于 2011-12-28T04:40:03.123 回答
0

该函数只是要在子类中实现的原型,它实际上并不存在于类中......因此它既不能在构造函数或析构函数中调用)。

没有函数的实现,所以简单地说,没有代码可以调用:)

调用构造函数/析构函数时,实现纯虚函数的子类不存在。

于 2011-12-28T04:39:38.037 回答
0

假设一个典型的虚函数在其抽象基类中没有显式实现。因此:

class Base
{
    public:
    virtual void area() = 0; 
    Base() { area();  }; 
    ~Base() { area(); };
};
void Base::area(){ print("Base::area()"); }

class Derived: public Base
{
    public:
    void area() override { print("Derived::area()"); };
    Derived() {  };
    ~Derived() {  };
};

int main() {
    Derived d;
}

实例化对象d后,执行控制将进入构造函数内部Base::Base(),并执行其主体和命中area(),并且由于area()编译器尚未满足 for 的实现,编译器将发出警告/错误,因为Derived(其中包含area()) 的覆盖版本实际上尚未创建。由于编译器知道(根据标准)纯虚函数必须在所有派生类中实现,而那些派生类不是由编译器创建的;此刻的编译器不知道它应该去哪里。因此,当您的编译器卡住时,就会出现未定义的行为。

于 2021-10-09T11:23:41.237 回答