非虚拟成员函数是静态解析的。成员函数在编译时静态绑定,基于指向对象的指针(或引用)的类型。
相比之下,虚拟成员函数是在运行时动态绑定。如果类至少有一个虚成员函数,那么编译器会在对象的构造过程中在对象中放置一个名为 vptr(virtual table address) 的隐藏指针。
编译器为每个类创建一个 v-table,其中至少有一个虚函数。虚表包含虚函数的地址。它可以是虚函数指针的数组或列表(取决于编译器)
在一个虚函数的调度过程中,运行时系统跟随对象的 v-pointer(从类对象获取地址)到类的 v-table,然后将偏移量添加到基地址(vptr)并调用该函数。
上述技术的 空间成本 开销是名义上的:每个对象有一个额外的指针(但仅适用于需要进行动态绑定的对象),以及每个方法的额外指针(但仅限对于虚拟方法)。
时间成本开销也相当小:与普通函数调用相比,虚函数调用需要两次额外的提取(一次获取 v 指针的值,第二次获取地址的方法)。
这种运行时活动不会发生在非虚拟函数上,因为编译器会在编译时根据指针的类型专门解析非虚拟函数。
我举了一个简单的例子来更好地理解非虚函数和虚函数的绑定是如何发生的,以及虚函数机制是如何工作的。
#include<iostream>
using namespace std;
class Base
{
public:
virtual void fun()
{}
virtual void fun1()
{}
void get()
{
cout<<"Base::get"<<endl;
}
void get1()
{
cout<<"Base::get1"<<endl;
}
};
class Derived :public Base
{
public:
void fun()
{
}
virtual void fun3(){}
void get()
{
cout<<"Derived::get"<<endl;
}
void get1()
{
cout<<"Derived::get1"<<endl;
}
};
int main()
{
Base *obj = new Derived();
obj->fun();
obj->get();
}
vtable 如何为基类和派生类创建
会生成汇编代码以便更好地理解。
$ g++ virtual.cpp -S -o virtual.s
我已经分别从 virtual.s 中获取了 Base 和 Derived 类的 vtable 信息:
_ZTV4Base:
.quad _ZN4Base3funEv
.quad _ZN4Base4fun1Ev
_ZTV7Derived:
.quad _ZN7Derived3funEv
.quad _ZN4Base4fun1Ev
.quad _ZN7Derived4fun3Ev
如您所见,fun 和 fun1 只是 Base 类中的两个虚函数。 Base class(_ZTV4Base)的Vtable有两个虚函数的入口。 Vtable 没有非虚函数的入口。请不要与 fun(ZN4Base3funEv) 和 fun1(ZN4Base4fun1Ev) 的名称混淆,它们的名称被混淆了。
派生类vtable有树条目
- fun(_ZN7Derived3funEv) 覆盖函数
- fun1(_ZN4Base4fun1Ev) 继承自基类
- fun3(_ZN7Derived4fun3Ev) 派生类中的新函数
非虚函数和虚函数如何调用?
非虚函数
Derived d1;
d1.get();
subq $16, %rsp
leaq -16(%rbp), %rax
movq %rax, %rdi
call _ZN7DerivedC1Ev //call constructor
leaq -16(%rbp), %rax
movq %rax, %rdi
call _ZN7Derived3getEv //call get function
简单地告诉、获取和调用get(绑定发生在编译时)
非虚函数
Base *obj = new Derived();
obj->fun();
pushq %rbx
subq $24, %rsp
movl $8, %edi
call _Znwm //call new to allocate memory
movq %rax, %rbx
movq $0, (%rbx)
movq %rbx, %rdi
call _ZN7DerivedC1Ev //call constructor
movq %rbx, -24(%rbp)
movq -24(%rbp), %rax
movq (%rax), %rax
movq (%rax), %rax
movq -24(%rbp), %rdx
movq %rdx, %rdi
call *%rax //call fun
获取vptr,添加函数偏移,调用函数(绑定发生在运行时)
64 的汇编让大多数 c++ 程序员感到困惑,但如果有任何想讨论的,欢迎