什么是虚函数?
在C++中,在基类的成员函数声明前加上关键字 virtual 即可让该函数成为 虚函数,派生类中对此函数的不同实现都会继承这一修饰符。
为什么需要虚函数?
这涉及到面向对象程序设计中多态、动态绑定的概念。
进程的内存分布
如果你已经完全了解上述概念,那么这篇文章很适合你去深入了解虚函数~
2. C++中类的memory Layout为了更好地理解虚函数的内部实现,我们首先需要知道,C++的类中成员函数和成员变量在内存中的空间分配。
1. 我们从最普通的一个类说起~ class X { int x; float xx; static int count; public: X() {} ~X() {} void printInt() {} void printFloat() {} static void printCount() {} };如果我们在这个程序中定义了这个类的一个对象,那么这个类的内存分布如下图所示:
类的非静态成员变量会被保存在栈上,类的静态成员变量被保存在数据段,而类的成员函数被保存在代码段。
class X { int x; float xx; static int count; public: X() {} ~X() {} void printInt() {} void printFloat() {} static void printCount() {} };2. 含有虚函数的基类的内存分布
如果一个类中含有虚函数,那么为了实现动态绑定,编译器会在原来的代码中插入(augment)一个新的成员变量--一个成员指针 vptr, 这个指针指向一张包含所有虚函数的函数指针表 vtable. 当我们调用虚函数时,实际上是通过vptr这个指针来调用函数指针表vtable里面的某个函数指针来实现函数调用。
一般而言,这张vtable会在数据段,是静态的,每一个类仅有一张表。但是这不是死规定,这是由编译器的实现决定的。vptr这个指针和成员变量一致,存在在堆栈段,是每一个对象都会有的。
vtable中的第一个entry包含了当前类及其基类的相关信息,其余entry是函数指针。
现在来看一个例子~
class X { int x; float xx; static int count; public: X() {} virtual ~X() {} virtual void printAll() {} void printInt() {} void printFloat() {} static void printCount() {} };3. 含有虚函数的子类的内存分布
此时,基类的成员变量和成员函数相当于派生类的子对象,也就是说派生类会继承基类的vptr。这时会先为基类的成员函数和成员对象分配内存空间,然后再为派生类的自己的成员变量和成员函数分配空间。vptr会指向Y这个类的vtable
如果派生类写了一个不在基类里的新的虚函数,那么这个vtable会多出一行,行内的内容是指向这个新虚函数的函数指针。
class X { int x; public: X() {} virtual ~X() {} virtual void printAll() {} }; class Y : public X { int y; public: Y() {} ~Y() {} void printAll() {} };4. 含有虚函数、有多继承的子类的内存分布
有多个基类的派生类会有多个vptr, 用来指向继承自不同基类的vtable。也就是说,每一个有虚函数的基类都会有一个虚函数指针表。
我们来看一个Z类继承自X类和Y类的例子。
class X { public: int x; virtual ~X() {} virtual void printX() {} }; class Y { public: int y; virtual ~Y() {} virtual void printY() {} }; class Z : public X, public Y { public: int z; ~Z() {} void printX() {} void printY() {} void printZ() {} };3. 虚函数的内部实现
了解了虚函数在内存中的分配方式后,理解虚函数的实现以及动态绑定就变得非常简单了。
这里以多继承的子类的代码为例,上代码~
class X { public: int x; virtual ~X() {} virtual void printX() { cout<<"printX() in X"<<endl; } }; class Y { public: int y; virtual ~Y() {} virtual void printY() { cout<<"printY() in Y"<<endl; } }; class Z : public X, public Y { public: int z; ~Z() {} void printX() { cout<<"printX() in Z"<<endl; } void printY() { cout<<"printY() in Z"<<endl; } void printZ() { cout<<"printZ() in Z"<<endl; } }; int main(){ Y *y_ptr = new Z(); y_ptr->printY(); // OK y_ptr->printZ(); // Not OK, Y类的虚函数表中没有printZ()函数 y_ptr->y = 3; // OK y_ptr->z = 3;// not OK, Y类的空间中没有变量z }