【C++】C++ 多态的底层实现原理

1. 多态的定义与作用

多态指的是同一操作在不同对象上具有不同的表现。C++ 中多态分为两类:

  • 编译时多态(静态多态):如函数重载、运算符重载,通过编译器在编译时决定调用哪个函数。
  • 运行时多态(动态多态):主要通过继承和虚函数实现,调用函数时根据实际对象的类型动态决定调用哪个函数。

本文重点讨论 运行时多态,它是通过虚函数机制实现的。

2. 虚函数与虚函数表(vtable)

C++ 中,虚函数 是用 virtual 关键字修饰的成员函数,表示这个函数可以在派生类中被重写,并且可以通过基类指针或引用调用时执行派生类的函数版本。

class Base {
public:
    virtual void Print() {
        std::cout << "Base::Print" << std::endl;
    }
};

class Derived : public Base {
public:
    void Print() override {
        std::cout << "Derived::Print" << std::endl;
    }
};

在上面的例子中,Base 类的 Print 函数是虚函数,Derived 类重写了这个函数。当我们通过基类指针调用 Print 时,会动态决定调用 Base 还是 Derived 的版本。

底层实现上,虚函数的机制依赖于虚函数表(vtable)和虚函数指针(vptr)。

3. 虚函数表(vtable)

每个包含虚函数的类,编译器会为其生成一个 虚函数表(vtable)。虚函数表是一个存放虚函数地址的数组。当对象是某个类的实例时,它会有一个指向该类的虚函数表的指针,称为 虚函数指针(vptr)。

  • 虚函数表 (vtable):每个包含虚函数的类都有一个虚函数表,表中存储的是该类的虚函数指针。如果派生类重写了某个虚函数,虚函数表中对应的指针会指向派生类的版本。
  • 虚函数指针 (vptr):每个对象实例都有一个虚函数指针 vptr,它指向该对象所属类的虚函数表。

当通过基类指针调用虚函数时,编译器根据对象的 vptr 找到对应的 vtable,然后查找表中的虚函数指针并进行调用。这样,实现了动态绑定(运行时多态)。

4. 虚函数调用的底层过程

假设我们有如下代码:

Base* ptr = new Derived();
ptr->Print();

这段代码的底层执行过程如下:

  1. 对象构造:当 Derived 类对象构造时,编译器会在对象内存布局中加入一个 vptr,并将其指向 Derived 类的虚函数表。
  2. 调用虚函数:当 ptr->Print() 被调用时,程序会根据 ptr 指向的对象类型,通过 vptr 查找 Derived 类的虚函数表,找到 Print 函数的地址并进行调用。

虚函数的调用可以简化为以下伪代码:

void (*vptr)() = this->vptr[Print];
vptr();

这里,vptr 指向的是 Derived 类的虚函数表,vptr[Print] 表示从虚函数表中获取 Print 函数的地址,然后通过该地址调用实际的函数。

5. 内存布局中的虚函数指针

为了更好地理解虚函数表在内存中的表现,来看一下对象的内存布局。在一个对象中,虚函数指针通常是对象布局的第一个成员,它指向该对象所属类的虚函数表。

+--------------------+
| vptr (虚函数指针)   |
+--------------------+
| 非静态数据成员      |
+--------------------+

在运行时,当调用虚函数时,程序首先通过对象的 vptr 找到对应类的 vtable,然后查找并调用虚函数。因此,每次调用虚函数都会产生一次间接函数调用开销,这就是为什么虚函数会比普通成员函数稍慢一些的原因。

6. 多重继承中的虚函数表

在 C++ 中,多重继承 会导致一个类拥有多个基类。这时,每个基类都会有自己的虚函数表。为了支持多重继承,编译器会为每个基类维护不同的虚函数表和 vptr

例如:

class Base1 {
public:
    virtual void Func1() {}
};

class Base2 {
public:
    virtual void Func2() {}
};

class Derived : public Base1, public Base2 {
public:
    void Func1() override {}
    void Func2() override {}
};

Derived 类的对象中,它会有两个 vptr,分别指向 Base1Base2 的虚函数表。这种情况下,虚函数调用会更加复杂,因为编译器需要根据继承层次和实际对象类型找到正确的 vtable

7. RTTI 与动态类型识别

C++ 运行时类型识别(RTTI)是通过虚函数表实现的。RTTI 包括 typeiddynamic_cast 等操作,它们可以在运行时检查对象的实际类型。编译器会在虚函数表中增加额外的信息,用于支持类型识别操作。

  • typeid:用于获取对象的类型信息。
  • dynamic_cast:用于安全地将基类指针转换为派生类指针。它依赖于虚函数表中的信息,确保转换的正确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值