🔨
C++
虚函数和多态实现相关
虚函数是基类中用
关键字修饰的成员函数。虚函数允许在派
生类中被重写(覆盖)。当通过基类指针或引用调用虚函数时,将根
据对象的实际类型调用相应的函数,而不是根据指针或引用的类型。
virtual
多态是通过虚函数实现的。
多态性是指相同的函数调用表现出不同的行为。C++中的多态性主
要通过虚函数实现。当通过基类指针或引用调用虚函数时,将根据对
象的实际类型调用相应的函数。
它们的实现技术主要涉及了 虚函数表、虚指针和动态绑定。
1. 虚函数表(vtable)
每一个包含虚函数的类都有一个唯一的虚函数表,包含该类的所有虚函数地址。
c++标准没有规定vtable的具体实现,但是大多数的编译器都是以函数指针数组实现
C++
1
的。
内容:
虚函数指针:vtable中主要存储了指向该类虚函数实现的指针。
类型信息指针:通常还包括指向类型信息(RTTI)的指针,用于动态类型识
别。
偏移量:在多重继承的情况下,可能还包含一些偏移量信息
2. 虚指针(vptr)
每一个包含虚函数的类对象都会包含一个隐藏的指针。这个指针指向该类的虚函数
表。通过这个指针就可以找到虚函数表中的函数。
3. 动态绑定
当通过基类指针或引用调用虚函数时,会发生动态绑定。运行时系统会查找对象的
vptr,通过vptr找到vtable,然后调用vtable中相应的函数。
具体的寻找过程是通过被赋值的对象,例如:
vtable
Base* ptr = new Child();
// Base
Child
Base
是基类,
是继承了
的子类,show为虚函数
ptr->show();
/*
程序首先找到ptr所指向的对象。
从对象的内存分布种获取到vptr。
3.通过vptr找到对应的vtable。
4.在vtable中找到show函数的地址。
5.调用找到的函数和地址。
1.
2.
*/
虚函数表的顺序通常与声明的顺序一致。
C++
2
虚函数表与类的关系:
每一个类的虚函数表通常会包含该类所有可用的虚函数:
1. 继承的虚函数:如果子类没有重写父类的虚函数,那么还是指向父类的实现。
2. 重写的虚函数:子类重写父类的虚函数后,函数地址将会被子类函数地址覆盖。
3. 新增的虚函数:子类新增的虚函数会被添加到虚函数表的末尾。
+-------------------+
| &Derived::func1
|
| &Base::func2
|
重写的 func1
继承的 func2
| // 新增的 func3
//
//
| &Derived::func3
+-------------------+
纯虚函数
纯虚函数是在基类中声明但没有实现的虚函数,通常用“=0”来表
示。
对于纯虚函数:
通常,纯虚函数在虚函数表中的对应位置会存储一个特殊的地址。
这个地址可能指向一个内部的错误处理函数,如果有人试图调用这个纯虚函数,会触
发运行时错误。
菱形继承
菱形继承是指一个派生类继承自两个类,而这两个类又有一个共同的
基类。
a)
C++
不使用虚继承时:
D 类会包含 A 的两个副本,每个通过 B 和 C 继承一个。
3
类可能会有两个虚函数表,分别对应 B 和 C 的继承路径。
这可能导致歧义,需要显式指定使用哪个基类的函数。
b) 使用虚继承时:
D
class B : virtual public A { ... };
class C : virtual public A { ... };
虚继承确保 A 只有一个实例在 D 中。
D 的虚函数表结构会更复杂,通常包含:
1. 指向 A 的偏移量。
2. D 自己的虚函数表。
3. B 和 C 的虚函数表。
虚函数调用可能需要额外的间接寻址来找到正确的函数实现
实例化与析构
构造顺序:
1. 基类构造函数(如果有多个基类,按照声明顺序进行,但如果有虚继承,虚基类
首先被构造,确保只有一个虚基类实例)
2. 成员变量(按声明顺序)
3. 派生类构造函数
析构顺序:
1. 派生类析构函数
2. 成员变量(按声明顺序的逆序)
3. 基类析构函数
基类析构函数为什么要设置成虚函数
确保正确的析构顺序:
当通过基类指针删除派生类对象时,虚析构函数确保先调用派生类的析构函数,然后
C++
4
再调用基类的析构函数。这保证了对象的完整清理。
防止内存泄漏:
如果基类的析构函数不是虚函数,那么通过基类指针删除派生类对象时,只会调用基
类的析构函数,而不会调用派生类的析构函数,可能导致资源泄漏。
支持多态性:
虚析构函数允许在运行时动态确定要调用的析构函数,这与C++的多态性概念一致。
基类的构造函数为什么不能是虚函数
虚函数的工作原理依赖于虚函数表(vtable),而vtable指针是在构造函数中初始化
的。
在对象创建时,还没有完整的对象存在,因此无法进行多态调用。
构造函数的作用是初始化对象,而不是操作一个已存在的对象
C++
5