前言
C++多态分为运行时多态和编译时多态,编译时多态的实现主要是模板,重载等,运行时多态就是使用虚函数来实现的
运行时多态
虚函数的底层实现原理是依托于虚函数指针和虚函数表实现的,虚函数指针也是一个成员变量,是编译器自动生成的,存在于实例对象的最前面位置。
虚函数指针工作原理
虚函数指针是一个成员变量,其在构造函数之后才会初始化,所以构造函数是不可以设置成虚函数的。析构函数往往需要设置成虚函数,这是因为如果不设置成虚函数,父类指针指向子类对象删除时,那么只会调用父类析构函数,父类指针无法获取子类的成员函数。
首先,怎么在代码中获取虚函数:
- 虚函数指针
vptr
是指向虚函数表的,虚函数每项存储虚函数地址,不妨把虚函数看成是 V_FUN 类型 *vptr + 1
表示第 i 个虚函数地址,*(*vptr+i)
表示第 i 个虚函数- 所以
type(vptr) == V_FUN**
- 如何获取
vptr
呢,一般是对象的前8位(64位系统)或者前4位(32位系统) - 虚函数的表怎么排布的呢?就是按照代码中的先后顺序排布的
#include <iostream>
#include <string>
using namespace std;
class Father {
public:
virtual string f0() {
return "Father::f0()";
}
virtual string f1() {
return "Father::f1()";
}
virtual string f2() {
return "Father::f2()";
}
};
using V_FUN = string (*)();
template<class T>
void run_virtual_function(T t, const int n) {
auto vptr = reinterpret_cast<V_FUN **>(&t);
for (int i = 0; i < n; ++i) {
auto function_i = *(*vptr + i);
cout << "正在运行第[" << i << "]个虚函数:"
<< function_i() << endl;
}
}
int main() {
Father f{};
run_virtual_function(f, 3); // 把三个虚函数都运行了一遍
return 0;
}
虚函数不覆盖继承
如果子类不覆盖父类的虚函数,那么子类的虚函数表就是,拷贝了父类的虚函数表,然后在后面添加自己的虚函数
#include <iostream>
#include <string>
using namespace std;
class Father {
public:
virtual string f0() {
return "Father::f0()";
}
virtual string f1() {
return "Father::f1()";
}
virtual string f2() {
return "Father::f2()";
}
};
class Son: public Father {
virtual string g0() {
return "Son::g0()";
}
virtual string g1() {
return "Son::g1()";
}
virtual string g2() {
return "Son::g2()";
}
};
using V_FUN = string (*)();
template<class T>
void run_virtual_function(T t, const int n) {
auto vptr = reinterpret_cast<V_FUN **>(&t);
cout << "虚函数表的地址:" << *vptr << endl;
for (int i = 0; i < n; ++i) {
auto function_i = *(*vptr + i);
cout << "正在运行第[" << i << "]个虚函数:"
<< function_i() << endl;
}
}
int main() {
Father f{};
Son s{};
cout << "=============== 运行父类的虚函数 ===============" << endl;
run_virtual_function(f, 3);
cout << "=============== 运行子类的虚函数 ===============" << endl;
run_virtual_function(s, 6);
return 0;
}
虚函数覆盖继承
如果子类虚函数覆盖父类虚函数,那么,会把子类虚函数表中项改变,即指向父类的函数变成指向子类自己的,不管子类对应的函数还是不是虚函数。
#include <iostream>
#include <string>
using namespace std;
class Father {
public:
virtual string f0() {
return "Father::f0()";
}
virtual string f1() {
return "Father::f1()";
}
virtual string f2() {
return "Father::f2()";
}
};
class Son: public Father {
virtual string g0() {
return "Son::g0()";
}
virtual string g1() {
return "Son::g1()";
}
virtual string g2() {
return "Son::g2()";
}
string f0() override {
return "Son::f0()";
}
string f1() override {
return "Son::f1()";
}
string f2() override {
return "Son::f2()";
}
};
using V_FUN = string (*)();
template<class T>
void run_virtual_function(T t, const int n) {
auto vptr = reinterpret_cast<V_FUN **>(&t);
cout << "虚函数表的地址:" << *vptr << endl;
for (int i = 0; i < n; ++i) {
auto function_i = *(*vptr + i);
cout << "正在运行第[" << i << "]个虚函数:"
<< function_i() << endl;
}
}
int main() {
Father f{};
Son s{};
cout << "=============== 运行父类的虚函数 ===============" << endl;
run_virtual_function(f, 3);
cout << "=============== 运行子类的虚函数 ===============" << endl;
run_virtual_function(s, 6);
return 0;
}
多重继承
多重继承的话,如下所示,加入子类继承了父类和母类,那么就会生成两个虚函数指针,先继承谁的就谁的在前面。然后子类的虚函数拼接在第一个基类的虚函数后面,如果有覆盖,就覆盖掉对应的。
#include <iostream>
#include <string>
using namespace std;
class Father {
public:
virtual string f0() {
return "Father::f0()";
}
virtual string f1() {
return "Father::f1()";
}
virtual string f2() {
return "Father::f2()";
}
};
class Mother {
public:
virtual string f0() {
return "Mother::f0()";
}
virtual string f1() {
return "Mother::f1()";
}
virtual string f2() {
return "Mother::f2()";
}
};
class Son: public Father, public Mother{
virtual string g0() {
return "Son::g0()";
}
virtual string g1() {
return "Son::g1()";
}
virtual string g2() {
return "Son::g2()";
}
};
using V_FUN = string (*)();
template<class T>
void run_virtual_function(T t, const int n, const int offset) {
auto vptr = reinterpret_cast<V_FUN **>(&t) + offset;
cout << "虚函数表的地址:" << *vptr << endl;
for (int i = 0; i < n; ++i) {
auto function_i = *(*vptr + i);
cout << "正在运行第[" << i << "]个虚函数:"
<< function_i() << endl;
}
}
int main() {
Father f{};
Mother m{};
Son s{};
cout << "=============== 运行父类的虚函数 ===============" << endl;
run_virtual_function(f, 3, 0);
cout << "=============== 运行母类的虚函数 ===============" << endl;
run_virtual_function(m, 3, 0);
cout << "=============== 运行子类的父虚函数 ===============" << endl;
run_virtual_function(s, 6, 0);
cout << "=============== 运行子类的母虚函数 ===============" << endl;
run_virtual_function(s, 3, 1);
return 0;
}
多态原理
经过上述可以看出,为什么使用父类指针可以运行子类的函数了,那是因为,父类指针实际对象还是子类,获取的虚函数指针也还是子类的,顺着虚函数指针去取值,还是会取得子类的虚函数。
PS:但是多重继承中,还是可以通过母类指针来运行母类函数,是因为直接获取地址会自动偏移到母类的虚函数地址。
#include <iostream>
#include <string>
using namespace std;
class Father {
public:
int* a; // 增加了一个变量
virtual string f0() {
return "Father::f0()";
}
virtual string f1() {
return "Father::f1()";
}
virtual string f2() {
return "Father::f2()";
}
};
class Mother {
public:
int* a; // 增加了一个变量
virtual string f0() {
return "Mother::f0()";
}
virtual string f1() {
return "Mother::f1()";
}
virtual string f2() {
return "Mother::f2()";
}
};
class Son: public Father, public Mother{
virtual string g0() {
return "Son::g0()";
}
virtual string g1() {
return "Son::g1()";
}
virtual string g2() {
return "Son::g2()";
}
// string f0() override {
// return "Son::f0()";
// }
};
using V_FUN = string (*)();
template<class T>
void run_virtual_function(T& t, const int n, const int offset) {
auto vptr = reinterpret_cast<V_FUN **>(&t) + offset;
cout << "对象地址:" << &t << endl;
cout << "虚函数表的地址:" << *vptr << endl;
for (int i = 0; i < n; ++i) {
auto function_i = *(*vptr + i);
cout << "正在运行第[" << i << "]个虚函数:"
<< function_i() << endl;
}
}
int main() {
Father f{};
Mother m{};
Son s{};
cout << "=============== 运行父类的虚函数 ===============" << endl;
run_virtual_function(f, 3, 0);
cout << "=============== 运行母类的虚函数 ===============" << endl;
run_virtual_function(m, 3, 0);
cout << "=============== 运行子类的父虚函数 ===============" << endl;
run_virtual_function(s, 6, 0);
cout << "=============== 运行子类的母虚函数 ===============" << endl;
run_virtual_function(s, 3, 2); // 需要偏移16个字节(父类虚函数指针,父类的int*)
// &s 是
Father* fs = &s;
Mother* ms = &s;
cout << "=============== 使用母类运行子类地址的母虚函数 ===============" << endl;
run_virtual_function(*ms, 3, 0);
cout << "=============== 使用父类运行子类的父类虚函数 ===============" << endl;
run_virtual_function(*fs, 3, 0);
cout << "==============================" << endl;
cout << "运行父: " << (*fs).f0() << endl;
cout << "运行母: " << (*ms).f0() << endl;
cout << "子类地址:" << &s << endl;
cout << "父指针对象地址:" << &(*fs) << endl; // 0x7ffdb0028e80
cout << "母指针对象地址:" << &(*ms) << endl; // 0x7ffdb0028e90
return 0;
}
可以看出内存布局是:父类虚函数指针-父类成员变量-母类虚函数指针-母类成员变量
然后如果使用父类或者母类指针指向子类对象,会自动偏移到对应位置,参考0x7ffdb0028e80->0x7ffdb0028e90
,就是自动偏移了16 bits
。