虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的?
参考回答
虚函数表(vtable)是针对类的,而不是针对对象的。
– 每个含有虚函数的类在编译时会生成一个虚函数表,表中存储该类的所有虚函数地址。
– 同一个类的所有对象共用一张虚函数表,而每个对象中包含一个隐藏的虚指针(vptr
),指向类的虚函数表。
– 虚指针的初始化在对象的构造函数中完成。
详细讲解与拓展
1. 虚函数表是针对类的
- 虚函数表(vtable):
- 虚函数表是编译器为每个包含虚函数的类生成的表,用于存储该类的所有虚函数地址。
- 如果类没有虚函数,则不会生成虚函数表。
- 虚函数表是一个静态结构,编译时生成,运行时不变。
- 虚函数表的特点:
- 针对类生成:每个类只有一张虚函数表,所有对象共享。
- 存储虚函数地址:表中的每一项是一个虚函数的地址。如果子类重写了某个虚函数,虚函数表中会将该项替换为子类的实现地址。
2. 虚指针(vptr)是针对对象的
- 每个对象有一个隐藏的虚指针(
vptr
),指向它所属类的虚函数表。 - 虚指针的作用:
- 在运行时,虚指针帮助对象找到其所属类的虚函数表。
- 调用虚函数时,通过虚指针访问虚函数表中的地址,完成动态绑定。
示例:
运行原理:
1. 类的虚函数表:
– Base
类的虚函数表(vtable):
“`
vtable(Base):
+——————-+
| Base::func1 |
| Base::func2 |
+——————-+
“`
– `Derived` 类的虚函数表(vtable):
“`
vtable(Derived):
+——————-+
| Derived::func1 | // 覆盖了 Base::func1
| Base::func2 | // 未覆盖,继承自 Base
+——————-+
“`
- 对象的虚指针(vptr):
baseObj
的虚指针(vptr
)指向Base
类的虚函数表。derivedObj
的虚指针(vptr
)指向Derived
类的虚函数表。
- 调用过程:
- 调用
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)
- 如果
baseObj
和baseObj2
是Base
类的两个对象,它们的虚指针都指向Base
的虚函数表。
4. 子类重写虚函数时的虚函数表变化
- 当子类重写了基类的虚函数时:
- 子类的虚函数表中会将该函数的地址替换为子类的实现。
- 没有被重写的虚函数,仍然继承基类的实现。
示例:
虚函数表:
– Base
的虚函数表:
“`
vtable(Base):
+——————-+
| Base::func1 |
| Base::func2 |
+——————-+
“`
– `Derived` 的虚函数表:
“`
vtable(Derived):
+——————-+
| Derived::func1 | // 覆盖了 Base::func1
| Base::func2 | // 继承自 Base
+——————-+
“`
总结
- 虚函数表是针对类的:每个类有一张虚函数表,存储该类的所有虚函数地址。
- 同一个类的所有对象共享虚函数表:对象通过虚指针(
vptr
)访问类的虚函数表。 - 虚指针是针对对象的:每个对象有一个独立的虚指针(
vptr
),指向该对象所属类的虚函数表。 - 子类重写虚函数时的表更新:子类的虚函数表会替换基类中对应虚函数的地址,而未被重写的函数仍然使用基类的实现。
通过这种机制,C++实现了动态绑定和运行时多态。