base.function()
调用就是一个“静态绑定”的函数调用:// 静态绑定
#include <iostream>
#include <cstdlib>
using namespace std;
class Base {
public:
void function() { cout << "Base::function" << endl; }
virtual void sayhello() { cout << "Hello!" << endl; }
};
int main()
{
Base *base = new Base();
base->function(); // function在编译期间就知道是调用Base类中的function函数
delete base;
return EXIT_SUCCESS;
}
call _ZN4Base8functionEv
的指令,而_ZN4Base8functionEv
正是base.function()
函数的入口名称。这也就意味着程序在“执行前”,编译器就已经知道在此处应该调用是函数base->function()
,而不是其他的函数,并将相关的指令固化下来。这就是“静态绑定”。main:
.LFB1459:
.loc 1 12 0
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
.loc 1 13 0
movl $1, %edi
call _Znwm@PLT
movq %rax, -8(%rbp)
.loc 1 14 0
movq -8(%rbp), %rax
movq %rax, %rdi
call _ZN4Base8functionEv ; !! 这里 !! 直接使用call指令调用Base类中的function()成员函数
.loc 1 15 0
movq -8(%rbp), %rax
movl $1, %esi
movq %rax, %rdi
call _ZdlPvm@PLT
.loc 1 16 0
movl $0, %eax
.loc 1 17 0
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
call _ZN4Base8functionEv
指令。那么,为什么实现多态就必须要动态绑定呢,静态绑定不行吗?我们一起来看下面这段代码:// 动态绑定
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
class Base {
public:
virtual void function() { cout << "Base::function" << endl; }
virtual void sayhello() { cout << "Hello!" << endl; }
};
class DeriveA : public Base{
public:
virtual void function() { cout << "DeriveA::function" << endl; }
};
class DeriveB : public Base{
public:
virtual void function() { cout << "DeriveB::function" << endl; }
};
int main()
{
Base *ptr = nullptr;
if (time(nullptr) % 2) // 这里time(nullptr)获取当前系统的unix时间戳, 它为奇数和偶数概率各为一半
ptr = new DeriveA(); // 当时间戳为奇数时, 创建DeriveA
else
ptr = new DeriveB(); // 当时间戳为偶数时, 创建DeriveB
ptr->function();
delete ptr;
return EXIT_SUCCESS;
}
time(nullptr)
获取当前系统的unxi时间戳,当时间戳为奇数时ptr
指向DeriveA
类对象,而当时间戳为偶数时ptr
指向DeriveB
类对象。当编译器编译ptr->function()
这条语句时,编译器不知道ptr
的背后是一个DeriveA
类还是一个DeriveB
类,而由于要实现多态性,就必须保证当ptr
指向DeriveA
的时候调用DeriveA
中的function()
,当ptr
指向DeriveB
的时候调用DeriveB
中的function()
,而ptr
指向谁这只能 在程序运行起来的时候才能知晓。因此,面向对象编程所需要的多态性就必须依赖“动态绑定”才能实现。当然,如果没有使用虚函数,不同函数不具备多态性,编译器在编译时就不会理睬ptr
具体指向什么, 直接执行静态绑定。Base
,DeriveA
,DeriveB
。
*__vptr
。这个*__vptr
指针是类的实例被创建的时候,编译器自动为其设置的。如果我们把这个指针显示地展现出来,大概就像这样:// 动态绑定
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
class Base {
public:
FunctionPointer *__vptr; //!隐藏指针! 指向虚表
virtual void function() { cout << "Base::function" << endl; }
virtual void sayhello() { cout << "Hello!" << endl; }
};
class DeriveA : public Base {
public:
FunctionPointer *__vptr; //!隐藏指针! 指向虚表
virtual void function() { cout << "DeriveA::function" << endl; }
};
class DeriveB : public Base {
public:
FunctionPointer *__vptr; //!隐藏指针! 指向虚表
virtual void function() { cout << "DeriveB::function" << endl; }
};
在上图中,每个类中的* __vptr
指向该类的虚拟表。对于被子类覆写了虚函数,在虚表中对应指向该函数的条目将指向该子类中的该函数(如DeriveA
类中的function
函数);如果子类没有覆写父类的虚函数,在虚表中对应指向该函数的条目将指向该子类的父类中的该函数(如DeriveA
类中的sayhello
函数)
首先,我们创建一个DeriveA
子类对象da
:
int main()
{
DeriverA da ;
//...
}
之后,我们让基类指针指向该对象:
int main()
{
DeriverA da;
Base *bda = &da;
//...
}
我们注意到由于bda
是一个Base
类型的指针,因此通过bda
指针我们只能访问DeriverA
类中的“基类部分”(比如function
函数)。然而,我们还要注意* __vptr
也是属于这个所谓的“基类部分”的,因此通过bda
也可以访问这个指针。但是,bda->__vptr
指向的是DeriverA
的虚表!因此,即使bda
指针是Base
类型,它仍然可以访问DeriverA
的虚表(通过__vptr
指针)。
最后,我们来分析一下使用bda
来调用function()
的过程中发生了什么。首先,程序认识到function()
是一个虚拟函数。其次,程序使用bda->__vptr
来访问DeriverA
的虚表。最终,通过DeriverA
的虚表找到需要调用的function()
——即DeriverA::function()
。因此,bda->function()
解析为DeriverA::function()
!
int main()
{
DeriverA da;
Base *bda = &da;
bda->function();
//...
}
如果我们使用图来表示就是:
* __vptr
来获得恰当的虚拟表;__vptr
指针进行解引用得到虚表;3. 索引虚表中的函数,找到需要调用的函数)来找到要调用的函数,而不是对普通的间接函数调用执行2个操作(1.对对象指针解引用,2.找到需要调用的函数),或对直接函数调用执行1个操作。__vptr
指针。因此该类的每个对象会多占用一个指针变量的内存空间。所以说,虚函数在空间上也有一定的成本。[1] www.learncpp.com
另外博主目前也只是个学生,如果博文中有任何错误,希望各位朋友帮忙指出!!谢谢!!
原文:https://www.cnblogs.com/Silgm/p/11290693.html