虚函数表:
单继承时的虚函数表:
1、无虚函数覆盖
假如现有单继承关系如下:
class Base
{
public:
virtual void x() { cout << "Base::x()" << endl; }
virtual void y() { cout << "Base::y()" << endl; }
virtual void z() { cout << "Base::z()" << endl; }
};
class Derive : public Base
{
public:
virtual void x1() { cout << "Derive::x1()" << endl; }
virtual void y1() { cout << "Derive::y1()" << endl; }
virtual void z1() { cout << "Derive::z1()" << endl; }
};
在这个单继承的关系中,子类没有重写父类的任何方法,而是加入了三个新的虚函数。Derive类实例的虚函数表布局如图示:
- Derive class 继承了 Base class 中的三个虚函数,准确的说,是该函数实体的地址被拷贝到 Derive 实例的虚函数表对应的 slot 之中。
- 新增的 虚函数 置于虚函数表的后面,并按声明顺序存放。
2、有虚函数覆盖
如果在继承关系中,子类重写了父类的虚函数:
class Base
{
public:
virtual void x() { cout << "Base::x()" << endl; }
virtual void y() { cout << "Base::y()" << endl; }
virtual void z() { cout << "Base::z()" << endl; }
};
class Derive : public Base
{
public:
virtual void x() { cout << "Derive::x()" << endl; } // 重写
virtual void y1() { cout << "Derive::y1()" << endl; }
virtual void z1() { cout << "Derive::z1()" << endl; }
};
则Derive类实例的虚函数表布局为:
相比于无覆盖的情况,只是把 Derive::x()
覆盖了Base::x()
,即第一个槽的函数地址发生了变化,其他的没有变化。
这时,如果通过绑定了子类对象的基类指针调用函数 x(),会执行 Derive 版本的 x(),这就是多态。
多重继承时的虚函数表
1、无虚函数覆盖
现有如下的多重继承关系,子类没有覆盖父类的虚函数:
class Base1
{
public:
virtual void x() { cout << "Base1::x()" << endl; }
virtual void y() { cout << "Base1::y()" << endl; }
virtual void z() { cout << "Base1::z()" << endl; }
};
class Base2
{
public:
virtual void x() { cout << "Base2::x()" << endl; }
virtual void y() { cout << "Base2::y()" << endl; }
virtual void z() { cout << "Base2::z()" << endl; }
};
class Derive : public Base1, public Base2
{
public:
virtual void x1() { cout << "Derive::x1()" << endl; }
virtual void y1() { cout << "Derive::y1()" << endl; }
};
对于 Derive 实例 d 的虚函数表布局,如下图:
可以看出:
- 每个基类子对象对应一个虚函数表。
- 派生类中新增的虚函数放到第一个虚函数表的后面
注意,这也就是为什么《深度探索C++对象模型里》对于多重继承的情况下要不断调整指针的指向的原因:
比如Base2* pb2=new Derive();
由于Base2是与Base1平级的基类,因此pb2的指向要从new Derive()的起始地址调整到Base2 subobject处,因为pb2只能调用Derived与Base2的共有部分的函数,不能调用Base1部分的函数,因此要调整到Base2 subobject处,因为Base2的虚指针在此处,不然将使用Base1的虚指针进行跳转,从而不能调用Base2的方法。
而当delete pb2时又要跳转到new Derive()的起始处,因为派生类对析构函数是进行了重写的,而派生类中重写的函数将写入第一个基类,也就是Base1里的vptr所指向的虚表中,因此要调整到new Derive()首处,这样才能正确的调用析构函数。
2、有虚函数覆盖
将上面的多重继承关系稍作修改,让子类重写基类的 x() 函数:
class Base1
{
public:
virtual void x() { cout << "Base1::x()" << endl; }
virtual void y() { cout << "Base1::y()" << endl; }
virtual void z() { cout << "Base1::z()" << endl; }
};
class Base2
{
public:
virtual void x() { cout << "Base2::x()" << endl; }
virtual void y() { cout << "Base2::y()" << endl; }
virtual void z() { cout << "Base2::z()" << endl; }
};
class Derive : public Base1, public Base2
{
public:
virtual void x() { cout << "Derive::x()" << endl; } // 重写
virtual void y1() { cout << "Derive::y1()" << endl; }
};
相比于无覆盖的情况,只是将Derive::x()
覆盖了Base1::x()
和Base2::x()
而已,你可以自己写测试代码测试一下,这里就不再赘述了。
注:若虚函数是 private 或 protected 的,我们照样可以通过访问虚函数表来访问这些虚函数,即上面的测试代码一样能运行。
附:编译器对指针的调整
在多重继承下,我们可以将子类实例绑定到任一父类的指针(或引用)上。以上述有覆盖的多重继承关系为例:
Derive b;
Base1* ptr1 = &b; // 指向 b 的初始地址
Base2* ptr2 = &b; // 指向 b 的第二个子对象
- 因为 Base1 是第一个基类,所以 ptr1 指向的是 Derive 对象的起始地址,不需要调整指针(偏移)。
- 因为 Base2 是第二个基类,所以必须对指针进行调整,即加上一个 offset,让 ptr2 指向 Base2 子对象。
- 当然,上述过程是由编译器完成的。
Base1* b1 = (Base1*)ptr2;
b1->y(); // 输出 Base2::y()
Base2* b2 = (Base2*)ptr1;
b2->y(); // 输出 Base1::y()
这里,由于ptr2指向的是Base2子对象的地址,因此尽管b1是Base1型指针,同时也将ptr2转换成了Base1,但ptr2的地址仍然是Base2的地址,因此调用y()也将调用Base2中的y(); 同理,由于ptr1指向的是Base1的地址,因此尽管b2是Base2型指针,同时也将ptr1转换成了Base2,但ptr1的地址仍然是Base1的地址,因此调用y()也将调用Base1中的y() 其实,通过某个类型的指针访问某个成员时,编译器只是根据类型的定义查找这个成员所在偏移量,用这个偏移量获取成员。由于 ptr2 本来就指向 Base2 子对象的起始地址,所以b1->y()
调用到的是Base2::y()
,而 ptr1 本来就指向 Base1 子对象的起始地址(即 Derive对象的起始地址),所以b2->y()
调用到的是Base1::y()
。
父类:Father
子类:Son
1. Father* fa=new Son()
实例1:
#include<iostream>
using namespace std;
class Father{
public:
Father(){
cout << "this is the Father constructor!" << endl;
}
void watchTv(){
cout << "Father is watching tv!" << endl;
}
virtual void say(){
cout << "Father is saying!" << endl;
}
};
class Son :public Father{
public:
Son(){
cout << "this is the Son constructor!" << endl;
}
void watchTv(){
cout << "Son is watching tv!" << endl;
}
void say(){
cout << "Son is saying!" << endl;
}
};
int main(){
Father* fa = new Son();
fa->watchTv();
fa->say();
system("pause");
return 0;
}
运行结果:
结果分析:
1.首先运行代码 Father* fa=new Son();,会首先调用父类构造函数,再调用子类构造函数,所以第一、第二行如图所示;
2.运行代码fa->watchTv(),根据fa的类型,因为是Father类型的,所以会先查看Father类中的watchTv函数,因为不是虚函数,所以直接调用,结果如第三行所示;
3.运行代码fa->say(),根据fa的类型,因为是Father类型的,所以先查看Father类中的say函数,因为是虚函数,所以会查看虚函数表,查看实现实例,找到子类Son有实现虚函数say,所以会调用子类Son的say函数,结果如第四行所示。
实例2: 在构造函数中调用虚函数
#include<iostream>
#include<string>
using namespace std;
class Father{
public:
Father():val("HunanTv"){
cout << "this is the Father constructor!" << endl;
say();
}
virtual void watchTv(){
cout << "Father is watching tv:" <<val<< endl;
}
void say(){
cout << "Father is saying to watchTv!" << endl;
watchTv();
}
string val;
};
class Son :public Father{
public:
Son(){
cout << "this is the Son constructor!" << endl;
say();
}
virtual void watchTv(){
val = "GuangdongTv";
cout << "Son is watching tv:"<<val << endl;
}
};
int main(){
Father* fa = new Son();
fa->say();
system("pause");
return 0;
}
运行结果:
结果分析:
1.运行代码Father* fa=new Son(),会先调用父类构造函数,父类构造函数中会执行以下操作:a)首先给变量val赋值为“HunanTv",其次,调用函数say(),此时会输出前两行内容;b)因为say()函数会调用虚函数watchTv(),而且在子类中有watchTv的实例实现,但是根据输出结果第三行发现,调用的还是父类的虚函数,为什么呢?因为,父类构造函数中调用虚函数,表现的虚函数为父类的虚函数,所以结果为第三行的显示;c)父类构造函数调用完后,执行子类构造函数,因为子类没有定义say()函数,所以调用继承自父类的say()函数,所以输出结果为第四、第五行;d)say函数调用虚函数watchTv(),因为是在子类Son的构造函数中调用虚函数,所以调用的是子类的watchTv,所以输出结果如第六行所示;e)执行完子类构建后,执行fa->say();,首先看fa的类型,所以调用父类的say(),say()调用虚函数watchTv时,因为watchTv在子类中有实例实现,所以会调用子类的watchTv,所以输出结果如第七、第八行所示。
总结:
1.当在构造函数中调用虚函数时,虚函数表现为该类中虚函数的行为,即父类构造函数调用虚函数,则虚函数为父类中的虚函数;子类构造函数中调用虚函数,则调用的是子类中的虚函数;
2.如果不是在构造函数中调用虚函数,则会首先查看虚函数表,如果有实例实现,则调用实例。比如:父类中有虚函数watchTv,则调用父类中watchTv时,则因为子类实现了watchTv,则调用子类的watchTv。
#include<iostream>
using namespace std;
class Base1
{
public:
virtual void f() {cout<<"Base1::f()"<<endl;}
virtual void g() {cout<<"Base1::g()"<<endl;}
virtual void h() {cout<<"Base1::h()"<<endl;}
};
int main()
{
typedef void(*fun)();
fun pFun;
Base1 d;
cout<<"the virtual function table address is "<<(int*)(&d)<<endl;
cout<<"the first virtual function address is "<<(int*)*(int*)(&d)<<endl;
cout<<"the first virtual function address is "<<(int*)*(int*)(&d) +1<<endl;
pFun = (fun)*((int*)*(int*)(&d) + 1);
pFun();
}
附:
下面是一个关于多重继承的虚函数表访问的例程:
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void f() { cout << "Base1::f" << endl; }
virtual void g() { cout << "Base1::g" << endl; }
virtual void h() { cout << "Base1::h" << endl; }
};
class Base2 {
public:
virtual void f() { cout << "Base2::f" << endl; }
virtual void g() { cout << "Base2::g" << endl; }
virtual void h() { cout << "Base2::h" << endl; }
};
class Base3 {
public:
virtual void f() { cout << "Base3::f" << endl; }
virtual void g() { cout << "Base3::g" << endl; }
virtual void h() { cout << "Base3::h" << endl; }
};
class Derive : public Base1, public Base2, public Base3 {
public:
virtual void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
};
typedef void(*Fun)(void);
int main()
{
Fun pFun = NULL;
Derive d;
int** pVtab = (int**)&d;
//Base1's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);
pFun = (Fun)pVtab[0][0];
pFun();
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);
pFun = (Fun)pVtab[0][1];
pFun();
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);
pFun = (Fun)pVtab[0][2];
pFun();
//Derive's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);
pFun = (Fun)pVtab[0][3];
pFun();
//The tail of the vtable
pFun = (Fun)pVtab[0][4];
cout<<pFun<<endl;
//Base2's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
pFun = (Fun)pVtab[1][0];
pFun();
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
pFun = (Fun)pVtab[1][1];
pFun();
pFun = (Fun)pVtab[1][2];
pFun();
//The tail of the vtable
pFun = (Fun)pVtab[1][3];
cout<<pFun<<endl;
//Base3's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
pFun = (Fun)pVtab[2][0];
pFun();
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
pFun = (Fun)pVtab[2][1];
pFun();
pFun = (Fun)pVtab[2][2];
pFun();
//The tail of the vtable
pFun = (Fun)pVtab[2][3];
cout<<pFun<<endl;
return 0;
}