c++中虚函数表属于类还是属于对象?
在 C++ 中,虚函数表(Virtual Table,简称 vtable) 是实现多态性的关键机制之一。关于虚函数表的归属问题,常见的疑问是:
“虚函数表属于类?还是属于对象?”
答案是:
虚函数表属于类,而不是每个对象。
然而,为了更深入地理解这一概念,下面将详细解释虚函数表(vtable)和虚指针(vptr)的关系,以及它们如何在类和对象中协同工作。
1. 虚函数表(vtable)概述
-
虚函数表(vtable):这是一个由编译器生成的静态数据结构,用于支持运行时的动态绑定。每个包含虚函数的类(基类或派生类)都有一个对应的虚函数表。
-
虚指针(vptr):每个对象内部通常包含一个隐藏的指针,称为虚指针,指向所属类的虚函数表。这个指针由编译器自动管理,程序员通常无需直接操作。
2. 虚函数表属于类
-
类级别的虚函数表:
-
单一实例:对于每个具有虚函数的类,编译器生成一个唯一的虚函数表。无论创建多少个该类的对象,所有对象共享同一个虚函数表。
-
内容:虚函数表中存储的是该类中所有虚函数的地址。如果派生类覆写了基类的虚函数,虚函数表中的对应条目将指向派生类的实现。
-
示例:
class Base { public: virtual void func1() { /* ... */ } virtual void func2() { /* ... */ } }; class Derived : public Base { public: void func1() override { /* ... */ } // 覆写 Base::func1 void func3() { /* 非虚函数 */ } };
Base
类有一个虚函数表vtable_Base
,包含func1
和func2
的地址。Derived
类有一个虚函数表vtable_Derived
,func1
指向Derived::func1
,func2
继承自Base
,如果未覆写则指向Base::func2
,并且新增的func3
不在虚函数表中(因为它不是虚函数)。
-
3. 虚指针属于对象
-
对象级别的虚指针(vptr):
-
每个对象都有一个虚指针:当创建一个对象时,编译器会自动在对象内嵌入一个指向其类的虚函数表的指针(
vptr
)。 -
指向虚函数表:这个虚指针指向所属类的虚函数表。例如,
Base
类的对象指向vtable_Base
,Derived
类的对象指向vtable_Derived
。
-
-
运行时多态的实现:
- 当通过基类指针或引用调用虚函数时,程序会通过
vptr
查找虚函数表,从而调用实际对象类中对应的函数实现,实现动态绑定。
- 当通过基类指针或引用调用虚函数时,程序会通过
-
示例:
Base* obj = new Derived(); obj->func1(); // 调用 Derived::func1 obj->func2(); // 调用 Base::func2(如果 Derived 未覆写 func2)
- 尽管
obj
是Base
类型的指针,但它实际指向一个Derived
对象。 obj
的vptr
指向vtable_Derived
,因此调用func1
时会执行Derived::func1
,而调用func2
时,如果Derived
没有覆写func2
,则执行Base::func2
。
- 尽管
4. 视觉化理解
类和虚函数表的关系
类 Base ------------------> vtable_Base
| func1 -> Base::func1
| func2 -> Base::func2
类 Derived ---------------> vtable_Derived
| func1 -> Derived::func1
| func2 -> Base::func2
对象和虚指针的关系
对象 baseObj (Base 类型)
| vptr -> vtable_Base
对象 derivedObj (Derived 类型)
| vptr -> vtable_Derived
5. 总结
-
虚函数表(vtable) 是 类级别 的数据结构,每个具有虚函数的类都有自己的虚函数表。
-
虚指针(vptr) 是 对象级别 的,每个对象包含一个指向其所属类虚函数表的指针。
-
这种设计使得不同类的对象可以共享相同的虚函数表(如果它们没有覆写虚函数),同时支持运行时的多态性,通过虚指针动态绑定到正确的函数实现。
-
override
关键字 主要用于在编译时确保子类正确覆写基类的虚函数,它本身并不直接影响虚函数表的结构或行为,但正确使用override
可以避免潜在的覆写错误,确保多态性正常工作。