C ++标准规定禁止从构造函数或析构函数调用纯虚函数。这是什么原因?为什么标准会设置这样的限制?
答案 0 :(得分:19)
在运行类析构函数时,所有子类析构函数都已已运行。调用由其子析构已经运行的子类定义的虚方法是无效的。
在构造函数中调用虚方法时存在类似的限制。您不能为其构造函数尚未运行的子类调用虚方法。
答案 1 :(得分:5)
同样的原因,当你倾倒粉底或撕开粉底时,你不能住在房子里。在构造函数完成之前,该对象仅部分构造。一旦析构函数启动,对象就会被部分破坏。纯虚函数只能在处于正常状态的对象上调用,否则需要确定调用哪个函数实现的结构可能不存在。
答案 2 :(得分: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.
我包含限定符“(still)”,因为纯虚函数被赋予定义 - 并且不再是“纯粹的” - 在类层次结构中的某个点上。这有点奇怪,但请考虑标准的这一部分:
如果一个类至少有一个纯虚函数,则它是抽象的。 [注意:这样的功能可能会被继承:见下文。 ]
显然,在基础中具有纯虚函数定义的派生类本身不一定是抽象的。只有“纯度”的意义不是普遍适用于虚函数本身,而是适用于保持纯粹的阶级等级的层次,上述陈述才能成立。
结果:如果函数 - 从调用构造函数/析构函数层次结构的角度来看 - 已经被定义,则可以使用明确定义的行为调用它。
通过对标准中未定义的内容的理解,我们可以回答您的问题:“这是什么原因?标准为什么要设置这样的限制?”
原因在于,必须在派生类构造函数启动之前完全构造基类,因为派生类的构造可以在基础对象上运行。类似地,基础析构函数必须在派生的析构函数之后运行,因为后者可能仍希望访问基础对象。鉴于这种必要的排序,编译器无法安全地分派到派生类虚函数,因为派生类的构造函数尚未运行以建立派生类成员状态,或者派生类的析构函数已调用其他数据的析构函数成员和对象状态再次不再保证可用。
对于非纯虚函数,编译器可以并且确实依赖于调用已经构造且尚未被破坏的层次结构中最派生类所知的函数的最专用定义。但是,根据定义,纯虚函数是尚未指定实现的函数,并且在类层次结构中,函数是纯的,没有实现可以调用。
虚拟分派机制的典型实现可以通过在基类的虚拟分派表中具有指针来表示这一点,该基类包含设置为0,未初始化或指向某些“提升警报”功能的纯虚函数。随着派生类构造函数的连续层启动,指向虚拟调度表的指针将被其自己的VDT的地址覆盖。那些覆盖实现的派生类将指向它们自己的函数定义,这将成为任何更多派生类的默认值,这些类本身不指定新实现。这个关键的隐式指针到VDT成员将在派生类析构函数完成时向后移动通过相同的VDT列表,确保任何虚拟调用都在类层次结构中的不受欢迎的层上运行。但是,当定义了虚函数的第一个类的析构函数运行时,未来的VDT将再次缺少对实际实现的引用。
答案 3 :(得分:2)
回想一下,从构造函数/析构函数中调用“非纯”虚函数忽略了函数是虚函数的事实,总是调用类中的实现,而不是派生类中的实现建造。这就是为什么你不能从构造函数或析构函数中调用纯虚拟的原因:就它们而言,你的纯虚函数没有实现。
答案 4 :(得分:0)
该函数只是一个在子类中实现的prototyope,它实际上并不存在于类中......所以它不能在构造函数或析构函数中调用)。
没有函数的实现,所以,简单地说,没有代码可以调用:)
调用构造函数/析构函数时,实现纯虚函数的子类不存在。