C++ 为何构造函数和析构函数中不能调用虚函数?


前言

之前看过很多书都提到 C++ 在构造函数和析构函数中不能调用虚函数,但当时的原因记得是构造时没有虚函数指针,析构时虚函数指针已经析构了,所以不能调用,现在想来似乎有些问题,毕竟虚函数指针初始化可以在调用语句前,析构可以先调用,最后才析构

以下用 ctor(constructor) 代替构造函数,dtor(destructor) 代替析构函数

接下来将详细的介绍具体的原因,顺便介绍下是否可以将 ctor 和 dtor 声明为虚函数

例子

#include <bits/stdc++.h>
using namespace std;

class Base {
public:
    Base() { f(); }
    ~Base() { f(); }
    virtual void f() { std::cout << "Base" << std::endl; } 
};
class Derived : public Base
{
public:
    Derived() : Base() {}
    virtual void f() { std::cout << "Derived" << std::endl; }
};
int main() {
    Derived d;
}
// outputs:
// Base
// Base
// outputs as the vtable still points to Base::f() when Base::Base() is run

可以将 C++ 的构造顺序看成搭积木一样(先搭里面再外面),在 Base 中调用多态函数,只会调用 Base 自己的方法而不是派生类的方法

可以将 C++ 的析构顺序看成剥洋葱一样(先剥外面再里面),在 Base 中调用多态函数,只会调用 Base 自己的方法而不是派生类的方法

之所以这样做可以解释为 Base 构造先于 Derived,如果在 Base 中调用的 virtual 函数用的 Derived 的实现,而 Derived 中的变量还没有实例化,此时若 virtual 函数调用 Derived 的变量就会出问题,而且还有另一个理由,在构造 Base 的对象时就该把它当成 Base 来构造,而不该还牵扯到 Derived

同理也对应于析构函数,Base 析构前 Derived 就已经析构了

构造析构顺序可以看这里

总结

  • 根据《Effective C++》的条款 09:绝不在构造和析构过程中调用虚函数可知,在构造函数中虽然可以调用虚函数,但是强烈建议不要这样做。因为基类的构造的过程中,虚函数不能算作是虚函数。若构造函数中调用虚函数,可能会导致不确定行为的发生。

ctor:

  • 虚函数对应一个 vtable(虚函数表),类中存储一个 vptr 指向这个 vtable。如果 ctor 是虚函数,就需要通过 vtable 调用,可是对象没有初始化就没有 vptr,无法找到 vtable,所以 ctor 不能是虚函数。

dtor:

  • dtor 为虚函数,并且一般情况下基类 dtor 要定义为虚函数,非基类没有必要,因为会降低性能。
  • 只有在基类 dtor 定义为虚函数时,调用操作符 delete 销毁指向对象的基类指针时,才能准确调用派生类的析构函数(从该级向上按序调用虚函数),才能准确销毁数据,否则只会析构基类的 dtor
  • dtor 可以是纯虚函数,含有纯虚函数的类是抽象类,此时不能被实例化。但派生类中可以根据自身需求重新改写基类中的纯虚函数。

参考


文章作者: Mou shuai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mou shuai !
评论
  目录