上篇文章我们知道了虚表的存在,虚表中存储了虚函数的指针,所以sizeof()展现出来会包括指针的大小,那么今天我们从原理的角度来理解一下多态。
下面这段代码中,Buy()函数,如果传入的是Person调用的就是Person::BuyTicket(),传Student调用的是Student::BuyTicket。这样就构成了多态,而多态的调用实现,是依靠运行时,去指向对象的虚表中查调用的函数地址。
class Person
{
public:
Person(const char* name = "张三")
:_name(name)
{}
virtual void BuyTicket()
{
cout << _name << "购票,需要排队,每人 100 ¥" << endl;
}
protected:
string _name;
};
class Student : public Person
{
public:
Student(const char* name)
:_name(name)
{}
virtual void BuyTicket()
{
cout << _name << "购票,需要排队,每人 50 ¥" << endl;
}
private:
string _name;
};
void Buy(Person* p)
{
p->BuyTicket();
}
int main()
{
Person p("张三");
Buy(&p);
Student st("张同学");
Buy(&st);
return 0;
}
通过监视窗口我们可以发现:
多态调用:运行时决议,运行时才确定函数的地址
普通函数:编译时决议,编译时确认调用函数的地址

如下面这个和上面那个p->BuyTicket()对比就知道,动态绑定和静态绑定的区别:

class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
private:
int _b = 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 _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
通过调试时我们发现似乎有些问题,理论上派生类d,应该会有三个虚函数(继承基类的两个,新增加的两个),但是我们发现虚表中只有两个指针,我们看不到Func3()和Func4(),这里是编译器隐藏了这两个函数,可以认为是VS的一个Bug。
这里我们采用比较底层的方式打印出虚表的指针:
// 打印虚表并执行函数
void PrintVFTable_Safe(void** vtable, int max_entries = 10)
{
cout << "Virtual Table Address: " << vtable << endl;
if (vtable == nullptr) {
cout << "Invalid vtable pointer!" << endl;
return;
}
for (int i = 0; i < max_entries; ++i)
{
// 检查地址是否有效
if (vtable[i] == nullptr || (uintptr_t)vtable[i] < 0x1000) {
cout << " [" << i << "]: END OF TABLE" << endl;
break;
}
cout << " [" << i << "]: " << vtable[i];
// 直接执行函数
typedef void(*FuncPtr)();
FuncPtr func = (FuncPtr)vtable[i];
cout << " -> ";
func(); // 执行函数
// 安全限制,避免无限循环
if (i >= max_entries - 1) {
cout << " ... (reached max entries)" << endl;
break;
}
}
cout << endl;
}
int main()
{
Base b;
Derive d;
void** vtable_b = *(void***)(&b);
void** vtable_d = *(void***)(&d);
cout << "=== Base Virtual Table ===" << endl;
PrintVFTable_Safe(vtable_b);
cout << "=== Derive Virtual Table ===" << endl;
PrintVFTable_Safe(vtable_d);
return 0;
}
下面我们给出一段多继承的代码,来分析一下:
#include <iostream>
using namespace std;
class Base1
{
public:
virtual void Func1()
{
cout << "Base1::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base1::Func2()" << endl;
}
private:
int _b1 = 1;
};
class Base2
{
public:
virtual void Func1()
{
cout << "Base2::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base2::Func2()" << endl;
}
private:
int _b2 = 1;
};
class Derive :public Base1, public Base2
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
virtual void Func3()
{
cout << "Derive::Func3()" << endl;
}
private:
int _d1 = 2;
};
// 打印虚表并执行函数
void PrintVFTable_Safe(void** vtable, int max_entries = 10)
{
cout << "Virtual Table Address: " << vtable << endl;
if (vtable == nullptr) {
cout << "Invalid vtable pointer!" << endl;
return;
}
for (int i = 0; i < max_entries; ++i)
{
// 检查地址是否有效
if (vtable[i] == nullptr || (uintptr_t)vtable[i] < 0x1000) {
cout << " [" << i << "]: END OF TABLE" << endl;
break;
}
cout << " [" << i << "]: " << vtable[i];
// 直接执行函数
typedef void(*FuncPtr)();
FuncPtr func = (FuncPtr)vtable[i];
cout << " -> ";
func(); // 执行函数
// 安全限制,避免无限循环
if (i >= max_entries - 1) {
cout << " ... (reached max entries)" << endl;
break;
}
}
cout << endl;
}
int main()
{
Derive d;
void** vtable_d = *(void***)(&d);
PrintVFTable_Safe(vtable_d);
return 0;
}构成这样一个继承关系:

我们通过调试先来看一下对象d的虚函数指针:

然后通过打印的结果我们可看出派生类新增的虚函数一般会储存在第一个继承基类的虚函数表中:

通过下面这张图大家应该可以更好地理解:

(本篇完)