C++多态之带有虚函数的菱形继承与菱形虚拟继承

一、菱形继承

那就先从菱形继承开始复习

如下代码:

#include<iostream>  
using namespace std;

class A
{
public:
	int _a;
};

class B : public A
{
public:
	int _b;
};

class C : public A
{
public:
	int _c;
};

class D : public C, public B
{
public:
	int _d;
};

int main()
{
	D dd;
	cout << sizeof(dd) << endl;

	dd.B::_a = 1;
	dd._b = 3;

	dd.C::_a = 2;
	dd._c = 4;

	dd._d = 5;
	B bb;
	C cc;
	cout << sizeof(bb) << endl;

	system("pause");
	return 0;
}

B、C中的_a都是来自于A,其实两个_a是同一个,但是因为B、C各自继承,产生了两份,造成了数据冗余由于D继承于B、C,B、C继承于A,所以D中的_a不知道是来自B还是来自C,于是产生了二义性

二、菱形虚拟继承

解决上述问题的方法就是将B和C的继承变为虚继承,在子类继承父类时,在访问限定符前加上virtual就可以虚继承。代码如下:

#include<iostream>  
using namespace std;  
  
class A  
{  
public:  
    int _a;  
};  
  
class B :virtual public A  
{  
public:  
    int _b;  
};  
  
class C :virtual public A  
{  
public:  
    int _c;  
};  
  
class D : public C, public B  
{  
public:  
    int _d;  
};  
  
int main()  
{  
    D dd;  
    cout << sizeof(dd) << endl;  
  
    dd._a = 1;  
    dd._b = 3;  
  
    dd._a = 2;  
    dd._c = 4;  
  
    dd._d = 5;  
    B bb;  
    C cc;  
    cout << sizeof(bb) << endl;  
  
    return 0;  
}  

可见加了virtual之后,_a会单独存放,B和C共享_a,于是解决了数据冗余与二义性。

从上图可以发现对象dd的内存布局与虚继承之前有很大的区别,首先cc对象和bb对象的内存空间中都分别多了一个存储着一个地址的空间,而把它们_a变量放在了成员变量的最底下,使_a成为一个公共的变量。显而易见,加了virtual之后的继承比普通继承的类多了4个字节,而在内存中看到在加了virtual的类中多了一个地址,请看以下分析:

分析一

分析二

由以上分析,发现虚表里的上面的地址存储的是寻找自己的虚表的偏移量

                                           下面的地址存储的是寻找公共的基类的偏移量

C的虚基表里找公共基类的偏移量是20,B的虚基表里找公共基类的偏移量是12,从各自的虚基表往下数

各自的偏移量就可以找到公共的基类A。用一个公共的位置管理公共继承的基类,这样就解决了二义性。


三、带有虚函数的菱形继承

在类的成员函数前加上virtual关键字,则这个成员函数称为虚函数。先看看代码:

#include<iostream>  
using namespace std;

//定义一个可以指向对象里函数的函数指针  
typedef void(*func)();

//打印虚函数表  
void PrintVtable(int* vtable)
{
	printf("vtable:0x%p\n",vtable);
	
	for (size_t i = 0; vtable[i] != 0; ++i)
	{
		printf("第%d个虚函数地址:0x%p,->", i, vtable[i]);
		func f = (func)vtable[i];
		f();
	}
	cout <<"======================================="<< endl;
}

class A
{
public:
	int _a;

	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}
	virtual void func2()
	{
		cout << "A::func2()" << endl;
	}
};

class B :public A
{
public:
	int _b;
	virtual void func1()
	{
		cout << "B::func1()" << endl;
	}
	virtual void func3()
	{
		cout << "B::func3()" << endl;
	}
};

class C :public A
{
public:
	int _c;

	virtual void func1()
	{
		cout << "C::func1()" << endl;
	}
	virtual void func3()
	{
		cout << "C::func3()" << endl;
	}
};

class D : public B,public C
{
public:
	int _d;


	virtual void func4()
	{
		cout << "D::func4()" << endl;
	}
};


int main()
{
	D dd;

	dd.B::_a = 1;
	dd.C::_a = 2;
	dd._b = 3;
	dd._c = 4;
	dd._d = 5;


	//D类里继承B类的虚表   
	
	PrintVtable(*(int**)&dd);

	system("pause");
	return 0;
}

当发生继承时,如果派生类重写了基类的虚函数,那么派生类的对象中会修改基类的虚表,虚表中的函数指针会指向派生类自己重写的函数,如果派生类没有重写基类的虚函数,那么派生类不会改变那个虚函数的指向只是把它继承下来。


四、带有虚函数的菱形虚拟继承

在菱形虚拟继承的基础上加上了虚函数,代码如下:

#include<iostream>  
using namespace std;  
  
//定义一个可以指向对象里函数的函数指针  
typedef void(*func)();  
  
//打印虚函数表  
void printvtable(int* vtable)     
{  
    cout << "虚表地址>" << vtable << endl;  
  
    for (int i = 0; vtable[i] != 0; ++i)  
    {  
        printf("第%d个虚函数地址:0x%x,->", i, vtable[i]);  
        func f = (func)vtable[i];  
        f();  
    }  
    cout << endl;   
}  
  
class A  
{  
public:  
    int _a;  
  
    virtual void func1()  
    {  
        cout << "A::func1()" << endl;  
    }  
};  
  
class B :virtual public A  
{  
public:  
    int _b;  
    virtual void func2()  
    {  
        cout << "B::func2()" << endl;  
    }  
    virtual void func1()  
    {  
        cout << "B::func1()" << endl;  
    }  
};  
  
class C :virtual public A  
{  
public:  
    int _c;  
  
    virtual void func1()  
    {  
        cout << "C::func1()" << endl;  
    }  
};  
  
class D : public C,public B  
{  
public:  
    int _d;  
  
    virtual void func1()  
    {  
        cout << "D::func1()" << endl;  
    }  
    virtual void func3()  
    {  
        cout << "D::func3()" << endl;  
    }  
};  
  
int main()  
{  
    D dd;  
    B bb;  
    C cc;  
    A aa;  
  
    cout << sizeof(dd) << endl;  
  
    dd._a = 1;  
    dd._b = 3;  
  
    dd._a = 2;  
    dd._c = 4;  
  
    dd._d = 5;  
  
    cout << sizeof(bb) << endl;  
  
    //D类里继承B类的虚表  
    cout << "D:bb:0x" << ⅆ  
    int* vtabledd = (int*)(*(int*)&dd);   
    printvtable(vtabledd);  
  
    //D类里继承C类的虚表  
    cout << "D:cc:0x" << ((int*)&dd + 3);  
    int* vtablecc = (int*)(*((int*)&dd + 3));  
    printvtable(vtablecc);  
  
  
    //D类里继承A类的虚表  
    cout << "D:aa:0x" << ((int*)&dd + 6);  
    int* vtableaa = (int*)(*((int*)&dd +6));  
    printvtable(vtableaa);  
    return 0;  
}  
虚表:虚函数表,存的是虚函数->多态。 虚基表:存的是偏移量,解决二义性和数据冗余性。

当给bb对象里加了个fun2函数后发现dd的对象模型又多了四个字节,这四个字节是在dd对象中继承的bb的内存空间中多出来的,就是用来存放bb对象自己的虚表地址。

因为aa对象被两个对象所继承,所以aa的虚表被被公用,因此bbcc对象的虚函数就不能放在他们公共的虚表里,只能创建自己的虚表,把自己的虚函数放在自己的虚表。

然后dd 对象里自己的虚函数(非继承来的)存在第一个继承来的对象的虚表里。
下面看看菱形继承dd对象里的虚函数都分别在什么地方存储的:


1.dd对象继承重写aa对象的虚函数存储在继承来的aa对象的虚函数表里。

2.dd对象里自己的虚函数(非继承来的)存在第一个继承来的对象的虚表里。

3.因为aa对象被两个对象所继承,所以aa的虚表被被公用,因此bbcc对象的虚函数就不能放在他们公共的虚表里,只能创建自己的虚表,把自己的虚函数放在自己的虚表。

大致总结如下:


猜你喜欢

转载自blog.csdn.net/qq_40840459/article/details/80205074
今日推荐