[C++]多态是如何调用不同的函数对象的?

发布于:2024-04-24 ⋅ 阅读:(30) ⋅ 点赞:(0)

        多态调用不同的函数对象涉及C++中的虚函数表(VTable)、虚函数指针(VPtr)以及动态绑定机制。下面详细解析这一底层逻辑:

1. 虚函数表(VTable)与虚函数指针(VPtr)

        在C++中,当一个类包含至少一个虚函数时,编译器会为该类创建一个**虚函数表**(Virtual Function Table, VTable)。VTable是一个数据结构,通常是一个数组,其元素是对应虚函数的地址。每个类都有自己的VTable,且派生类会继承并可能扩展基类的VTable。

        同时,编译器会在每个含有虚函数的类的实例中插入一个隐藏的指针,称为**虚函数指针**(Virtual Function Pointer, VPtr)。VPtr指向该实例所属类的VTable。这样,每个类实例都可以通过其VPtr访问到其类的VTable,进而调用相应的虚函数。

2. 对象内存布局与VPtr位置

        一个包含虚函数的类实例在内存中的布局大致如下:

- VPtr:位于对象内存的起始位置(通常情况,具体取决于编译器实现)。
- 成员变量:紧跟在VPtr之后,按照声明顺序排列。
- 派生类独有的成员(如果有):如果该类是派生类,那么基类部分之后是派生类独有的成员变量和函数。

3. 多态调用过程

现在我们通过一个具体的例子来说明多态调用的底层逻辑:

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

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

int main() {
    Base* basePtr = new Derived();
    basePtr->funcA();
    delete basePtr;
    return 0;
}

在上述代码中,当执行`basePtr->funcA()`时,发生了多态调用:

1. 查找VPtr:

首先,CPU通过`basePtr`找到对象的起始地址,即VPtr所在位置

2. 获取VTable:

然后,CPU从VPtr处加载出指向VTable的地址。

3. 定位虚函数地址:

接着,CPU根据VTable的布局,索引到`funcA`在VTable中的位置,取出对应虚函数的地址。在本例中,basePtr实际指向的是Derived对象,因此VTable对应Derived类,其中的funcA条目指向Derived::funcA()

4. 执行函数:

最后,CPU跳转到刚刚获取的函数地址执行代码,即Derived::funcA()

4. 动态绑定与静态绑定对比

        相比之下,非虚函数或非通过基类指针/引用调用的虚函数调用是静态绑定的。在编译期间,函数调用的目标地址就已经确定。而多态调用是**动态绑定**(Runtime Binding),即函数调用目标在运行时根据对象的实际类型确定,这就是多态性得以实现的关键机制。

总结

        C++中的多态调用通过虚函数表(VTable)、虚函数指针(VPtr)以及动态绑定机制实现。当通过基类指针或引用调用虚函数时,编译器和运行时环境会依据对象的实际类型,通过其VPtr找到正确的VTable,再从VTable中查找出对应虚函数的地址并执行。这种机制确保了同一消息(函数调用)在不同对象(基类与派生类)上表现出不同的行为,实现了多态性。