虚函数有两种调用方式:
- 方案1,直接使用函数地址调用。
- 方案2,通过对象的虚函数表指针找到虚函数表,从而得到函数地址,完成调用。
应用场景主要有如下三种情况:
- 当对象直接调用时,采用方案 1 调用虚函数。
- 当对象的指针或者引用调用时,采用方案 2 调用虚函数。
- 类成员函数中调用虚函数。
(1)若是直接调用,则走方案 2 路线。
(2)若是使用 类名::虚函数,则走方案 1 路线。
栗子:
class CBase
{
public:
virtual void func() { std::cout << "CBase::func()" << std::endl; }
};
class CA : public CBase
{
public:
void func() { std::cout << "CA::func()" << std::endl; }
void func_1() { CBase::func(); }
void func_2() { func(); }
};
(1)当执行如下代码时,
CA A;
A.func();
CBase Base;
Base.func();
结果为
CA::func()
CBase::func()
转成汇编,
005D29F6 call CA::func (05D1325h)
005D29FE call CBase::CBase (05D11F9h)
可以发现,编译器采用的是直接调用的方式,与场景1相对应。
(2)当执行如下代码时,
CBase* pbase = new CA;
pbase->func();
结果为
CA::func()
转成汇编,
27: pbase->func();
005D2A8D mov eax,dword ptr [pbase]
005D2A90 mov edx,dword ptr [eax]
005D2A92 mov esi,esp
005D2A94 mov ecx,dword ptr [pbase]
005D2A97 mov eax,dword ptr [edx]
005D2A99 call eax
005D2A9B cmp esi,esp
005D2A9D call __RTC_CheckEsp (05D12F3h)
执行的方案是通过对象的虚函数表指针找到虚函数表,从而得到函数地址,完成调用。与场景2相对应。
(3)执行如下代码,
A.func_1();
A.func_2();
结果为
CBase::func()
CA::func()
转成汇编,
17: CBase::func();
0071284D mov ecx,dword ptr [this]
00712850 call CBase::func (071120Dh)
21: func();
007128AD mov eax,dword ptr [this]
007128B0 mov edx,dword ptr [eax]
007128B2 mov esi,esp
007128B4 mov ecx,dword ptr [this]
007128B7 mov eax,dword ptr [edx]
007128B9 call eax
007128BB cmp esi,esp
007128BD call __RTC_CheckEsp (07112F3h)
可以发现,前者是直接调用的方式 ,后者是通过虚函数表才找到的虚函数的地址。与场景3相对应。
(SAW:Game Over!)