Bootstrap

C++——虚函数表和虚基表

虚基表

在学习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()

 

最后,希望以这样图和代码的形式让你明白了多态的底层,还有虚基表。

;