1.在了解虚函数表之前先了解两个概念:
(1)虚函数
虚函数是在类的非静态成员函数前加virtual,则这个成员函数成为虚函数(并不是所有的成员函数能够定义为虚函数,如构造函数等),在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,实现多态性。
(2)虚函数表
C++中的虚函数的实现一般是通过虚函数表(Virtual Table)来实现的。简称为V-Table。 这张表解决了继承、覆盖的问题,指明了实际所应该调用的函数。
2.一个普通的基类的虚函数表
定义一个A类
class A
{
public:
virtual void f1()
{
cout << "A::f1()" << endl;
}
virtual void f2()
{
cout << "A::f2()" << endl;
}
virtual void f3()
{
cout << "A::f3()" << endl;
}
int _a;
};
要想知道虚函数表的构成可以先实例化一个对象a;再通过&a的地址得到对象a的地址,由于对象a的前四个字节才是虚表指针(我们所要得到的),因此我们可以将它强转为int *,得到虚表指针(指向虚函数表),再对它解引用得到它的内容,得到的是虚函数的地址,由于得到的是int类型的变量,所以还要再强转一次得到第一个虚函数的地址。
A a;
int *a_First_Fuction =((int *) *((int *)&a));
通过虚表中的函数指针来探究对象调用虚函数的顺序,将a_First_Fuction 传给PrintfVTable:
typedef void(*V_FUNC)();
void PrintfVTable(int * Vtable)
{
printf("Vtable:0x%p\n",Vtable);
for (size_t i = 0; Vtable[i]; i++)
{
void(*f)() = (void (*)()) Vtable[i];
//V_FUNC f = (V_FUNC)Vtable[i];
f();
}
}
可以很清楚的看到a的虚函数调用顺序,先定义的先调用,可以的到下图的结果
结论:一个普通的基类的虚函数地址按照声明顺序排列。
3.一个继承了A类的派生类B的虚函数表,但是B类的虚函数并没有覆盖A类的虚函数,没有构成重写。
class B :public A
{
public:
virtual void f4()
{
cout << "B::f4()" << endl;
}
virtual void f5()
{
cout << "B::f5()" << endl;
}
int _b;
};
实例化一个对象b
B b;
int *b_First_Function = ((int *)*((int *)&b));
将b_First_Fuction 传给PrintfVTable
通过得到的结果可以看到如下虚函数表:
结论:由图可以看到无虚函数覆盖的子类所构成的虚函数表的函数指针存放顺序为父类的虚函数在子类的前面,按照声明顺序排列。
3.一个继承了A类的派生类C的虚函数表,并且构成重写,还有自己的虚函数。
class C :public A
{
virtual void f1()
{
cout << "C::f1()" << endl;
cout << endl;
}
virtual void f5()
{
cout << "C::f5()" << endl;
cout << endl;
}
int _c;
};
实例化一个对象c
C c;
int *c_First_Function = ((int *)*((int *)&c));
将c_First_Fuction 传给PrintfVTable
通过得到的结果可以看到如下虚函数表:
结论:当存在虚函数的重写时,虚函数表中覆盖的函数用子类的虚函数代替其余顺序不变,先完成父类的虚函数地址的存放,再存放子类的非覆盖的函数。
4.一个菱形继承的类D,拥有自己的虚函数,还有与父类构成重写的虚函。
class A
{
public:
virtual void f1()
{
cout << "A::f1()" << endl;
cout << endl;
}
virtual void f2()
{
cout << "A::f2()" << endl;
cout << endl;
}
virtual void f3()
{
cout << "A::f3()" << endl;
cout << endl;
}
virtual void f4()
{
cout << "A::f4()" << endl;
cout << endl;
}
int _a;
};
class B :public A
{
public:
virtual void f1()
{
cout << "B::f1()" << endl;
cout << endl;
}
virtual void f2()
{
cout << "B::f2()" << endl;
cout <<endl;
}
int _b;
};
class C :public A
{
public:
virtual void f1()
{
cout << "C::f1()" << endl;
cout << endl;
}
virtual void f3()
{
cout << "C::f3()" << endl;
cout << endl;
}
virtual void f6()
{
cout << "C::f6()" << endl;
cout << endl;
}
int _c;
};
class D :public B,public C
{
public:
virtual void f1()
{
cout << "D::f1()" << endl;
cout << endl;
}
virtual void f5()
{
cout << "D::f5()" << endl;
cout << endl;
}
int _d;
};
由于代码太过繁杂,用图表示四个类之间的关系,
实例化对象d,通过调试窗口的内存窗口可以看到对象d似乎有两个虚表,而且打印只能看到继承B这部分的虚函数再加A类的。
通过指针的移动,将两个虚表全部打印出来:
可以得到对象模型以及虚函数表为:
可以看到菱形继承的B类和C类都拥有自己的虚函数表,而子类的虚函数被放在了第一个被声明的父类中(即public D :public B, public C)。B类的虚表拥有A类、B类、C类的虚函数,B类的虚表排列顺序为:假如D类的虚函数构成重写,就换成D类的虚函数,D类没有构成重写,就看B类是否构成重写,B类构成重写排B类的虚函数,然后再排A类没有在B类和C类没有构成重写的虚函数,接着排B类没有构成重写的虚函数,最后排D类没有构成重写的函数,C类的虚表类似:只是虚表内只能放置A类和C类拥有的虚函数,再按照是否构成重写在D类和C类查找(不能排列A类和C类没有的虚函数比如f5()。)
5.菱形继承的类D,且为虚继承,每个子类都拥有一个没有构成重写的虚函数和一个构成重写的虚函数。
class A
{
public:
virtual void f1()
{
cout << "A::f1()" << endl;
cout << endl;
}
virtual void f2()
{
cout << "A::f2()" << endl;
cout << endl;
}
int _a;
};
class B :virtual public A
{
public:
virtual void f1()
{
cout << "B::f1()" << endl;
cout << endl;
}
virtual void f3()
{
cout << "B::f3()" << endl;
cout << endl;
}
int _b;
};
class C :virtual public A
{
public:
virtual void f1()
{
cout << "C::f1()" << endl;
cout << endl;
}
virtual void f4()
{
cout << "C::f4()" << endl;
cout << endl;
}
int _c;
};
class D :public B,public C
{
public:
virtual void f1()
{
cout << "D::f1()" << endl;
cout << endl;
}
virtual void f5()
{
cout << "D::f5()" << endl;
cout << endl;
}
int _d;
};
用图表示菱形继承的虚继承,其中同名函数都构成了重写
实例化对象d,通过调试窗口的内存窗口可以看到对象d似乎有三个虚表(共有五个地址除了两个虚基表之外,还有三个地址),通过打印这三个地址可以看到如下现象:
通过上图可以得到d对象模型以及虚继承下的虚表的构成
注:这里每个子类都有一个虚函数是父类没有的所以有三个虚表
可以看到菱形继承且存在虚继承的时候对象d的三个虚表构成为:A类有一个自己的虚表,存放自己的虚函数,如果子类重写了A类的虚函数,那么就用子类的替代(首先应该从D类找是否构成虚函数的重写,再从 C类和B类找是否构成重写);由于在继承的时候B类先声明,所以将D类虚函数放在B类中,A类有的虚函数去掉,在B类D和类找A类没有的虚函数,剩下的构成重写的用子类D的虚函数替代就好了;C类的虚表就是直接将A类有的虚函数去除(如果全有的话,就少一张虚表),剩下的按照声明的顺序排列在C类的虚表中。
怎样计算对象的大小:
http://blog.csdn.net/skyroben/article/details/53207795
深入探究对象模型以及虚基表:
http://blog.csdn.net/skyroben/article/details/65939376