本篇文章主要讲解 多态底层原理 的相关内容
1. 多态原理
1.1 虚函数表
先看一下这段代码,计算一下sizeof(Base)
是多少:
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
char _ch = 'd';
};
我这里以32位机器为例:很多人会认为这里是8字节,但是肯定没那么简单,这里答案是
12字节
。为什么呢?
这里不但有两个成员变量,还有一个虚表指针:
__vfptr
:virtual function pointer
在一个含有虚函数的类中,一定会存在一个虚表指针,因为虚函数的地址会存放在虚表当中,那么派生类中的这个表都有什么呢?接着往下分析。
上面的代码进行改造:
- 增加一个派生类
Derive
去继承Base
Derive
中重写Func1
Base
再增加一个虚函数Func2
和一个普通函数Func3
代码如下:
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
通过监视窗口观察,发现基类和派生类虚表指针的地址是不同的
通过观察,我们发现,基类的虚函数表会复制到派生类的虚函数表中,如果派生类虚函数进行了重写,那么对应的虚函数地址就会改变,新函数地址会覆盖掉基类中的地址,因此重写又叫做覆盖。
因此,多态是如何保证父类的指针调用虚函数就访问父类,派生类的指针调用虚函数就访问派生类的呢?
在编译阶段,编译器会检查语法,看是否满足多态的两个条件:
- 虚函数重写
- 父类的指针或者引用调用虚函数
如果满足,就会在链接阶段直接在虚表找到对应的函数地址并调用;
如果不满足,就会在编译阶段根据类型确定函数的地址。
以下是不构成重写的情况:
class Person {
public:
// 这个函数没有进入虚函数表
void BuyTicket() { cout << "买票-全价" << endl; }
private:
int _i = 1;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
int _j = 2;
};
void Func(Person* p)
{
p->BuyTicket();
}
int main()
{
Person Mike;
Func(&Mike);
Person p1;
Func(&p1);
Student Johnson;
Func(&Johnson);
return 0;
}
2. 打印虚表
虚表本质上是一个函数指针数组,即是一个数组,存放的类型是函数指针类型,我们只需要知道函数指针数组的首地址就可以访问其中的所有元素了。
我们同样知道__vfptr
存放的就是首地址,取到他就好,这里可以用指针的强制转换。
先看一下这段代码:
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a = 1;
};
class Derive :public Base {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
private:
int b = 2;
};
这里Derive
中覆盖了Base
中的func1
,继承了Base
中的func2
,fun3
和func4
进入了虚表:
typedef void(*VFPTR)();
void PrintVFT(VFPTR* vft)
{
for (int i = 0; i < 4; ++i)
{
printf("%p->", vft[i]);
VFPTR v = vft[i];
(*v)();
}
}
int main()
{
Base b;
Derive d;
VFPTR* ptr = (VFPTR*)(*((int*)(&d)));
PrintVFT(ptr);
return 0;
}