虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的?

参考回答

虚函数表(vtable)是针对类的,而不是针对对象的。
– 每个含有虚函数的类在编译时会生成一个虚函数表,表中存储该类的所有虚函数地址。
– 同一个类的所有对象共用一张虚函数表,而每个对象中包含一个隐藏的虚指针(vptr),指向类的虚函数表。
– 虚指针的初始化在对象的构造函数中完成。


详细讲解与拓展

1. 虚函数表是针对类的

  • 虚函数表(vtable):
    • 虚函数表是编译器为每个包含虚函数的类生成的表,用于存储该类的所有虚函数地址。
    • 如果类没有虚函数,则不会生成虚函数表。
    • 虚函数表是一个静态结构,编译时生成,运行时不变。
  • 虚函数表的特点:
    • 针对类生成:每个类只有一张虚函数表,所有对象共享。
    • 存储虚函数地址:表中的每一项是一个虚函数的地址。如果子类重写了某个虚函数,虚函数表中会将该项替换为子类的实现地址。

2. 虚指针(vptr)是针对对象的

  • 每个对象有一个隐藏的虚指针(vptr),指向它所属类的虚函数表。
  • 虚指针的作用:
    • 在运行时,虚指针帮助对象找到其所属类的虚函数表。
    • 调用虚函数时,通过虚指针访问虚函数表中的地址,完成动态绑定。

示例:

#include <iostream>
using namespace std;

class Base {
public:
    virtual void func1() { cout << "Base func1" << endl; }
    virtual void func2() { cout << "Base func2" << endl; }
};

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

int main() {
    Base baseObj;
    Derived derivedObj;

    Base* basePtr = &baseObj;
    Base* derivedPtr = &derivedObj;

    basePtr->func1();     // 输出 "Base func1"
    derivedPtr->func1();  // 输出 "Derived func1"

    return 0;
}
C++

运行原理:
1. 类的虚函数表:
Base 类的虚函数表(vtable):

“`
vtable(Base):
+——————-+
| Base::func1 |
| Base::func2 |
+——————-+
“`
– `Derived` 类的虚函数表(vtable):
“`
vtable(Derived):
+——————-+
| Derived::func1 | // 覆盖了 Base::func1
| Base::func2 | // 未覆盖,继承自 Base
+——————-+
“`

  1. 对象的虚指针(vptr):
    • baseObj 的虚指针(vptr)指向 Base 类的虚函数表。
    • derivedObj 的虚指针(vptr)指向 Derived 类的虚函数表。
  2. 调用过程:
    • 调用 basePtr->func1() 时,通过 baseObj 的虚指针找到 Base 的虚函数表,调用 Base::func1
    • 调用 derivedPtr->func1() 时,通过 derivedObj 的虚指针找到 Derived 的虚函数表,调用 Derived::func1

3. 同一个类的两个对象如何维护虚函数表

  • 同一个类的所有对象共享同一张虚函数表。
  • 每个对象有一个独立的虚指针(vptr),指向相同的虚函数表。
  • 虚指针的管理:
    • 对象的构造函数负责初始化虚指针,将其指向该对象所属类的虚函数表。
    • 如果对象的类层次结构发生变化(如转换为子类),虚指针会更新为指向新的虚函数表。

图示:

Base 类的虚函数表(vtable):
+-------------------+
| Base::func1       |
| Base::func2       |
+-------------------+

两个对象的虚指针(vptr):
baseObj -> vtable(Base)
baseObj2 -> vtable(Base)
  • 如果 baseObjbaseObj2Base 类的两个对象,它们的虚指针都指向 Base 的虚函数表。

4. 子类重写虚函数时的虚函数表变化

  • 当子类重写了基类的虚函数时:
    • 子类的虚函数表中会将该函数的地址替换为子类的实现。
    • 没有被重写的虚函数,仍然继承基类的实现。

示例:

class Base {
public:
    virtual void func1() { cout << "Base func1" << endl; }
    virtual void func2() { cout << "Base func2" << endl; }
};

class Derived : public Base {
public:
    void func1() override { cout << "Derived func1" << endl; }
};
C++

虚函数表:
Base 的虚函数表:

“`
vtable(Base):
+——————-+
| Base::func1 |
| Base::func2 |
+——————-+
“`
– `Derived` 的虚函数表:
“`
vtable(Derived):
+——————-+
| Derived::func1 | // 覆盖了 Base::func1
| Base::func2 | // 继承自 Base
+——————-+
“`


总结

  1. 虚函数表是针对类的:每个类有一张虚函数表,存储该类的所有虚函数地址。
  2. 同一个类的所有对象共享虚函数表:对象通过虚指针(vptr)访问类的虚函数表。
  3. 虚指针是针对对象的:每个对象有一个独立的虚指针(vptr),指向该对象所属类的虚函数表。
  4. 子类重写虚函数时的表更新:子类的虚函数表会替换基类中对应虚函数的地址,而未被重写的函数仍然使用基类的实现。

通过这种机制,C++实现了动态绑定和运行时多态。

发表评论

后才能评论