虚基表
在学习C++继承过程中,都知道菱形继承是一个很大的坑。而菱形继承所带来的问题就是数据冗余和二义性的问题。
class A
{
public:
int _a;
};
class B : public A
{
public:
int _b;
};
class C : public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
上面的代码中,因为B和C都继承了A,又因为D继承了BC,所以这就导致了一个问题,BC中都存了A中的成员变量。这就是数据冗余、而且很浪费内存。下面我用32位下来验证一下答案。
很明显,B和C中都有一份_a,而解决这种问题的方法就是虚继承。让B和C都虚继承A,这就可以解决数据冗余和二义性的问题。
class A
{
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
};
class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
下面创建D的对象d,然后看它的内存就很明了了。下图是菱形虚拟继承中内存的 对象成员模型,这里可以分析出D对象中将A放到了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过B和C的两个指针,指向了一张表,这两个指针叫做虚基表指针,指向的就是虚基表,通过虚基表中的偏移量就可以找到下面的A。
虚函数表
在C++中,虚函数的主要作用其实是为了实现多态。关于多态,多态其实就是多种形态,就是父类的指针指向了子类的对象,然后子类对象去调用子类的成员函数。这其实是一种泛型编程,上面就是一种动态动态。
静态多态是在编译时确定的,它的例子就是函数重载,通过函数名修饰规则来取代。
关于虚函数,一个类中如果有虚函数修饰的成员函数,那么每个对象的前4/8个字节就是存放的虚函数表的指针,这个指针指向了虚函数表。
单继承(无虚函数的覆盖)
子类中没有重写任何父类的成员函数,所以在子类中的虚表就是如下图所示
typedef void(*Fun)();
Fun pFun = nullptr;
Derive d;
cout << "虚函数表地址: " <<*(int*)(&d) << endl;
cout << "虚函数表 - 第一个函数地址: " << (int**)(&d+0) << endl;
cout << "虚函数表 - 第二个函数地址: " << (int**)(&d+1) << endl;
cout << "虚函数表 - 第三个函数地址: " << (int**)(&d+2) << endl;
cout << "虚函数表 - 第四个函数地址: " << (int**)(&d+3) << endl;
cout << "虚函数表 - 第五个函数地址: " << (int**)(&d+4) << endl;
cout << "虚函数表 - 第六个函数地址: " << (int**)(&d+5) << endl;
pFun = (Fun) * ((int*)*(int*)(&d) + 0);
pFun();
pFun = (Fun) * ((int*)*(int*)(&d) + 1);
pFun();
pFun = (Fun) * ((int*)*(int*)(&d) + 2);
pFun();
pFun = (Fun) * ((int*)*(int*)(&d) + 3);
pFun();
pFun = (Fun) * ((int*)*(int*)(&d) + 4);
pFun();
pFun = (Fun) * ((int*)*(int*)(&d) + 5);
pFun();
上面我去打印d对象虚表时,因为对象的头4/8个字节存放的就是虚函数表指针的地址,所以当我们&d的时候,强转成(int*)就可以取到它的头四个字节。通过结果可以发现,和我们的预期是一样的。
单继承(有虚函数的覆盖)
当子类去重写(覆盖)父类中的f()、g()成员函数的时候,我们来Derive对象中的虚函数表中是怎么样的???
class Base
{
public:
virtual void f() { cout << "Base:f" << endl; }
virtual void g() { cout << "Base:g" << endl; }
virtual void h() { cout << "Base:h" << endl; }
};
class Derive : public Base
{
public:
virtual void f() { cout << "Derive:f" << endl; }
virtual void g() { cout << "Derive:g" << endl; }
virtual void h1() { cout << "Derive:h1" << endl; }
};
typedef void(*Fun)();
Fun pFun = nullptr;
Derive d;
cout << "虚函数表地址: " <<*(int*)(&d) << endl;
cout << "虚函数表 - 第一个函数地址: " << (int**)(&d+0) << endl;
cout << "虚函数表 - 第二个函数地址: " << (int**)(&d+1) << endl;
cout << "虚函数表 - 第三个函数地址: " << (int**)(&d+2) << endl;
cout << "虚函数表 - 第四个函数地址: " << (int**)(&d+3) << endl;
cout << "虚函数表 - 第五个函数地址: " << (int**)(&d+4) << endl;
pFun = (Fun) * ((int*)*(int*)(&d) + 0);
pFun(); //Derive::f()
pFun = (Fun) * ((int*)*(int*)(&d) + 1);
pFun();//Derive::g()
pFun = (Fun) * ((int*)*(int*)(&d) + 2);
pFun();//Base::h()
pFun = (Fun) * ((int*)*(int*)(&d) + 3);
pFun();//Derive::h1()
可以看到我们子类去重写了 父类的虚函数,父类的指针指向了子类的对象,然后去调用子类的成员函数,所以这也就是为什么能形成多态的根本原因。
而父类指针指向了父类的对象,父类调用的就是父类的成员函数,如上图所示,这就是父类对象中虚函数表中的模型。
多继承(无虚函数的覆盖)
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 f1() { cout << "Derive::f1()" << endl; }
virtual void g1() { cout << "Derive::g1()" << endl; }
virtual void h1() { cout << "Derive::h1()" << endl; }
};
可以看到子类中的虚函数是在第一个虚表中的,并且第一个继承的父类也是在虚表。
每个类中的虚函数都有一个自己对应的虚表。
多继承(有虚函数的覆盖)
子类中覆盖了父类的f()、g()函数,所以子类对象中的虚表应该是如下图所示
用下面代码去验证结果,Talking is cheap,Show me the code!!
typedef void(*Fun)();
Derive d;
Fun pFun = NULL;
cout << "虚函数表地址:" << *(int*)(&d) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&d) << endl;
// Invoke the first virtual function
pFun = (Fun) * ((int*)*(int*)(&d) + 0);
pFun();//Derive::f()
pFun = (Fun) * ((int*)*(int*)(&d) + 1);
pFun();//Derive::g()
pFun = (Fun) * ((int*)*(int*)(&d) + 2);
pFun();//Base1::h()
pFun = (Fun) * ((int*)*(int*)(&d) + 3);
pFun();//Derive::g1()
cout << "---------" << endl;
pFun = (Fun) * ((int*)*((int*)(&d) + 1) + 0);
pFun();//Derive::f()
pFun = (Fun) * ((int*)*((int*)(&d) + 1) + 1);
pFun();//Derive::g()
pFun = (Fun) * ((int*)*((int*)(&d) + 1) + 2);
pFun();//Base2::h()
cout << "----------" << endl;
pFun = (Fun) * ((int*)*((int*)(&d) + 2) + 0);
pFun();//Derive::f()
pFun = (Fun) * ((int*)*((int*)(&d) + 2) + 1);
pFun();//Derive::g()
pFun = (Fun) * ((int*)*((int*)(&d) + 2) + 2);
pFun();//Base3::h()
最后,希望以这样图和代码的形式让你明白了多态的底层,还有虚基表。