含有虚函数菱形的虚拟继承(没有对虚函数进行重写)

在VS2013编程,调试

问题 :  菱形继承会引来,二义性

1.源代码

</pre><pre name="code" class="cpp">#include <iostream>
using namespace std;

class Base

{
public:
	virtual void  FunTest()
	{
		cout << "Base::FunTest () " << endl;
	}
	virtual void  FunTest1()
	{
		cout << "Base::FunTest1 () " << endl;
	}
};

class C1 :virtual public Base
{
public:
	virtual void  FunTest2()
	{
		cout << "C1::FunTest2 () " << endl;
	}
};

class C2 :virtual public Base
{
public:
	virtual void  FunTest3()
	{
		cout << "C2::FunTest3 () " << endl;
	}
};

class Der : public C1, public C2
{
public:
	virtual void  FunTest4()
	{
		cout << "Der::FunTest4 () " << endl;
	}
	virtual void  FunTest5()
	{
		cout << "Der::FunTest5 () " << endl;
	}
	virtual void  FunTest6()
	{
		cout << "Der::FunTest6 () " << endl;
	}
};

typedef void(*vftab) ();

void Test()
{
	Der  d;
	cout << sizeof(d) << endl;
	cout << "-------C1---- " << endl;
	int *vfpt = (int *)(*(int *)&d);
	vftab vft = (vftab)(*(int *)vfpt);

	while (vft != 0)
	{
		vft();
		vfpt++;
		vft = (vftab)(*vfpt);
	}
	cout << endl;

	cout << "-------C2---- " << endl;
	vfpt = (int *)(*((int *)&d + 2));
	vft = (vftab)(*(int *)vfpt);

	while (vft != 0)
	{
		vft();
		vfpt++;
		vft = (vftab)(*vfpt);
	}
	cout << endl;

	cout << "-------Base---- " << endl;
	vfpt = (int *)(*((int *)&d + 4));
	vft = (vftab)(*(int *)vfpt);

	while (vft != 0)
	{
		vft();
		vfpt++;
		vft = (vftab)(*vfpt);
	}

}

int main()
{
	Test();

	getchar();
	return 0;
}

运行结果:

这些结果是怎么来的哪?为什么 d的大小为20个字节??

接下来就看看d的内存吧

现在知道了为什么d的大小为20个字节了吧!但是问题有来了,d的内存中存的都是些什么东西哪?

一步一步看吧!

貌似这些都是地址,那就看看这些地址有存了些什么

一.  看一下到 地址(0x 00 2a dd  2c)

可以从监视1看到 地址(0x 00 2a 11 59)是虚函数C1::FunTest2()的入口地址

可以从监视1看到 地址(0x 00 2a 12 53)是虚函数Der::FunTest4()的入口地址

可以从监视1看到 地址(0x 00 2a 13 de)是虚函数Der::FunTest5()的入口地址

可以从监视1看到 地址(0x 00 2a 10 05)是虚函数Der::FunTest6()的入口地址

总结1:地址(0x 00 2a dd  2c),应该是类Der从类C1继承下来虚表的地址,虚表中存的是类Der和类C1的虚函数的地址

二.  看一下到 地址(0x 00 2a dd  5c)

可以看到  0x 00 4e f9 30  + 0x 00 00 00 0c  = 0x 00 4e f9 3c

 总结2:地址(0x 00 2a dd  5c)下,应该是存的是偏移

三.    看一下到 地址(0x 00 2a dd  44)

可以从监视1看到 地址(0x 00 2a 10 69)是虚函数C2::FunTest3()的入口地址

 总结3:地址(0x 00 2a dd  44),应该是类Der从类C2继承下来虚表的地址,虚表中存的是类C2的虚函数的地址

四.   看一下到 地址(0x 00 2a dd  68)

可以看到  0x 00 4e f9 38  + 0x 00 00 00 04  = 0x 00 4e f9 3c

 总结4:地址(0x 00 2a dd  68)下,应该是存的是偏移

五.看一下到 地址(0x 00 2a dd  50)

可以从监视1看到 地址(0x 00 2a 12 c6) 是虚函数Base::FunTest()的入口地址

可以从监视1看到 地址(0x 00 2a 12 7b) 是虚函数Base::FunTest1()的入口地址

 总结5:地址(0x 00 2a dd  50),应该是类Der从类Base继承下来虚表的地址,虚表中存的是类Base的虚函数的地址

根据上面的一步一步的分析 :可以得到菱形虚拟继承(含有虚函数,但没有被重写)的模型:

看看这两个的偏移,就是它们保证了类 Der在继承 类Base的虚拟函数,以及类Base的数据成员的唯一性,从而避免了二义性的产生。

注意:在函数Test()中的地址偏移,是为了方便从内存中取得地址,查看里面是什么内容,如果每个类加上自己的数据成员,那就不是那样取值了,

             我说的是什么哪 ?意思就是说 :

            vfpt = (int *)(*((int *)&d + 2));

   vfpt = (int *)(*((int *)&d + 4));

          看见 2 和 4 了吧,说的就是这个

时间: 2024-11-15 22:41:47

含有虚函数菱形的虚拟继承(没有对虚函数进行重写)的相关文章

菱形的虚拟继承

问题引入: 如果有Base类,B1类,B2类,D类,如下图继承关系 那么按一般的继承来看,D类创造的对象会继承B1类的方法与成员,同时也会继承B2类的方法与成员: 接下来类B1 , B2 会分别去继承Base类的方法与成员,那么D类的对象在调用Base类的方法时,到底是继承B1类这边的Base,还是会继承B2类这边的Base,此时就会产生二义性 为了解决这个问题,就看看虚拟继承是怎么来解决这个二义性问题的 菱形的虚拟继承的源代码 #include <iostream> using namesp

菱形虚拟继承&虚函数表&对象模型

菱形继承: Assitant的菱形继承对象模型 Assitant中有两份Person成员,存在二义性和数据冗余. 所以我们引出了虚拟继承. virtual虚继.不会在子类中创建父类成员,但是子类中可以引用,就像指针一样.主要用在菱形继承,也叫钻石继承. 虚拟继承对象模型 class Student : vitrual public Person class Teacher : virtual public Peraon 虚函数表 通过一块连续内存来存储虚函数的地址.这张表解决了继承.虚函数(重写

虚函数和虚拟继承的内存分布

一.虚函数 (1)C++中的虚函数的主要作用:实现了多态的机制. (2)多态:用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. (3)多态要基于函数重载,所以如果子类没有重载父类的虚函数那是一件毫无意义的事情. 二.虚函数表 1.虚函数表:虚函数表C+

单继承与多继承中的虚函数表和虚函数指针

首先,我们了解一下何为单继承,何为多继承?? 单继承:一个子类只有一个直接父类. 多继承:一个子类有两个或多个直接父类. 单继承中的虚函数表分析: 示例程序: #include <iostream> using namespace std; typedef void(*FUNC)(); class Base { public: virtual void func1() { cout << "Base::func1()" << endl; } virt

C++ Primer 学习笔记_70_面向对象编程 --纯虚函数、容器与继承

面向对象编程 --纯虚函数.容器与继承 I.纯虚函数 在函数形参后面写上 =0 以指定纯虚函数: class Disc_item : public Item_base { public: double net_price(size_t) const = 0; //指定纯虚函数 }; 将函数定义为纯虚函数能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类的版本绝不会调用.重要的是,用户将不能创建Disc_item类型的对象. Disc_item discount; //Error Bulk

C++ Primer 学习笔记_70_面向对象编程 -纯虚函数、器皿与继承

面向对象编程 --纯虚函数.容器与继承 I.纯虚函数 在函数形参后面写上 =0 以指定纯虚函数: class Disc_item : public Item_base { public: double net_price(size_t) const = 0; //指定纯虚函数 }; 将函数定义为纯虚函数能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类的版本绝不会调用.重要的是,用户将不能创建Disc_item类型的对象. Disc_item discount; //Error Bulk

C++虚拟继承 类的内存布局

1. 单个虚拟继承 只是为了分析而已,实际中并没有太大的作用.跟虚拟继承相关的派生类对象的内存布局跟具体的编译器相关. (1)VS编译器:无论有无虚函数,必然含有虚基类表指针.虚基类表中的内容为本类实例的偏移和基类实例的相对偏移值.如果有虚函数,那么基类的虚函数表跟派生类的虚函数表是分开的. 在内存布局上,地址从低到高,顺序如下:派生类的虚函数表指针+虚基类表指针+派生类的成员变量+"间隔"(4个字节)+基类的虚函数表指针+基类的成员变量.派生类跟基类实例的位置关系跟普通继承正好相反.

深入理解虚表之非虚拟继承及虚拟继承

非虚拟继承 [带虚函数的类] class Base { public: virtual void FunTest1() { cout<<"Base::FunTest1()"<<endl; } virtual void FunTest2() { cout<<"Base::FunTest2()"<<endl; } int _data1; }; int main() { Base b; b._data1 = 0x01; re

谈谈c++中继承中的虚函数

c++继 承中的虚函数 c++是一种面向对象的编程语言的一个很明显的体现就是对继承机制的支持,c++中继承分很多种,按不同的分类有不同分类方法,比如可以按照基类的个数分为多继承和单继承,可以按照访问权限分为public继承.protected继承和private继承,按照是否是虚拟继承可以分为virtual继承和non-virtual继承.当然这里的分类标准都是有重叠的部分,比如,non-virtual继承又可以分为单继承和多继承.这里要讨论的是虚函数,因此主要从virtual和non-virt