C++ 中通过纯虚函数实现抽象类多态的底层机制
- 虚函数表(VTable)
- 在 C++ 中,每个包含虚函数(包括纯虚函数)的类都有一个虚函数表。虚函数表是一个函数指针数组,数组中的每一项都指向该类的一个虚函数的实现。当类中有纯虚函数时,虚函数表中对应纯虚函数的项通常为一个特殊的标记(比如 NULL 或者指向一个特殊的未实现函数的指针)。
- 对于派生类,如果派生类没有重写基类的纯虚函数,那么派生类的虚函数表中对应纯虚函数的项依然是基类虚函数表中的那个特殊标记,此时该派生类仍然是抽象类。如果派生类重写了纯虚函数,那么虚函数表中对应项就会被替换为派生类中重写函数的指针。
- 虚指针(VPtr)
- 每个包含虚函数(包括纯虚函数)的类的对象都有一个虚指针。这个虚指针指向该对象所属类的虚函数表。在对象构造时,虚指针会被正确初始化,指向对应的虚函数表。当通过基类指针或引用调用虚函数时,程序会首先根据对象的虚指针找到对应的虚函数表,然后在虚函数表中找到对应虚函数的指针,并调用该函数。这就实现了运行时多态,即根据对象的实际类型(而不是指针或引用的静态类型)来调用合适的虚函数实现。
派生类重写多个纯虚函数时确保多态正确实现的关键要点
- 函数签名必须一致
- 派生类中重写的纯虚函数的函数签名(包括函数名、参数列表和常量性)必须与基类中的纯虚函数完全一致。例如,如果基类中有
virtual void func(int a) const = 0;
,那么派生类中重写的函数必须也是 void func(int a) const
,否则编译器会认为这是一个新的函数,而不是重写,从而无法实现多态。
- 访问权限
- 派生类中重写的函数的访问权限不能比基类中纯虚函数的访问权限更严格。通常基类中的纯虚函数是
public
的,所以派生类中重写的函数也应该是 public
的。如果派生类将重写函数设为 private
或 protected
,会导致通过基类指针或引用无法访问到该函数,多态也就无法正确实现。
- 调用链的正确性
- 在重写的函数中,如果需要调用基类版本的函数(比如在执行派生类特有的操作之前或之后执行基类的一些通用操作),要确保调用的正确性。一般通过
BaseClass::func()
的方式调用基类版本的函数。例如:
class Base {
public:
virtual void func() = 0;
};
class Derived : public Base {
public:
void func() override {
Base::func();// 调用基类版本的函数(假设基类有实现)
// 执行派生类特有的操作
}
};
- 覆盖所有纯虚函数
- 如果派生类想要成为非抽象类(即可以创建对象),就必须重写基类中的所有纯虚函数。只要有一个纯虚函数没有被重写,该派生类仍然是抽象类,不能创建对象,也就无法在该派生类对象上正确实现多态。