【深入探索c++对象模型】data语义学二
单一继承中,base class 和derived class的对象都是从相同的地址开始,其间差异只在于derived class比较大,用以容纳自己的nonstatic members。
若vptr放在class object的起始处,如果base class没有虚函数而derived class有,那么单一继承的上述机制就被打破,把一个derived object转换为其base类型,就需要编译器的介入,用于调整地址。

虚拟继承
class ios1
{
int a;
};
class istream1 :public ios1
{
int b;
};
class ostream1 :public ios1
{
int c;
};
class iostream1 :public istream1, public ostream1
{
int d;
}; void main()
{
cout << sizeof(ios1) << " " << sizeof(istream1) << " " << sizeof(ostream1) << " " << sizeof(iostream1);
system("pause");
}
//vs2013输出为4 8 8 20
class ios1
{
int a;
};
class istream1 :public virtual ios1
{
int b;
};
class ostream1 :public virtual ios1
{
int c;
};
class iostream1 :public istream1, public ostream1
{
int d;
}; void main()
{
cout << sizeof(ios1) << " " << sizeof(istream1) << " " << sizeof(ostream1) << " " << sizeof(iostream1);
system("pause");
}
//输出为4 12 12 24
各类经由拷贝操作取得所有的nested virtual base class指针,放到derived class object之中。所以上述代码中的24为4个int成员的16个字节和继承自两个直接父类的指针8个字节。istream和ostream中都含有一个ios subobject,为了让iostream的对象布局中只有一个ios subobject,使用虚拟继承。
如何合并istream和ostream中的共同部分?
class如果内含一个或多个virtual base class subobjects,像istream那样,将被分割为两部分:一个不变局部和一个共享局部。不变局部中的数据,不管后继如何演变,总是拥有固定的offset,所以这一部分数据可以直接存取。共享局部(表现virtual base class subobject),其位置会因为每次的派生操作而有变化,所以只可以被间接存取(指针)。
一般的布局策略是先安排好派生类的不变部分,然后再建立其共享部分。
cfront编译器会在每一个派生类对象中安插一些指针,每个指针指向一个virtual base class,要存取继承得来的virtual base class members,可以通过相关指针间接完成
例子:
void point3d::operator+=(const point3d &rhs)
{
_x+=rhs._x;
_y+=rhs._y;
_z+=rhs._z;
}
在cfront策略下,这个运算符被内部转换为:
_vbcPoint2d->_x+=rhs._vbcPoint2d->_x;
_vbcPoint2d->_y+=rhs._vbcPoint2d->_y;
_z+=rhs._z;
以上模型有两个缺点:
1.每一个对象必须针对其每一个virtual base class背负一个额外的指针
2.由于虚继承串链的加长,导致间接存取层次的增加
为了第二个问题,编译器经由拷贝操作取得所有的nested virtual base class指针,放到derived class object之中,以空间换取时间。
为了不让对象因为其virtual base class的数目的变化导致指针数目变化,进而改变存取所需空间,可以引入virtual base class table,每一个class object如果有一个或多个virtual base class,就会由编译器安插一个指针,指向virtual base class table。该table中存放指向virtual base class的指针(microsoft编译器实现)。
第二个解决方法是在virtual function table中放置virtual base class 的offset。virtual function table可经由正值或负值来索引,如果是正值,就索引到virtual function,如果是负值,则索引到virtual base class offset。

虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,也并不常用,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要因为这样只会降低效率(通过指针来间接存取虚基类的成员)和占用更多的空间。
笔试,面试中常考的C++虚拟继承的知识点:
第一种情况: 第二种情况: 第三种情况 第四种情况:
class a class a class a class a
{ { { {
virtual void func(); virtual void func(); virtual void func(); virtual void func();
}; }; char x; char x;
class b:public virtual a class b :public a }; };
{ { class b:public virtual a class b:public a
virtual void foo(); virtual void foo(); { {
}; }; virtual void foo(); virtual void foo();
}; };
四种情况分别求sizeof(a),sizeof(b),结果如下:
第一种:4,12
第二种:4,4
第三种:8,16
第四种:8,8
若是虚继承,派生类通过vbptr指向父类,派生类大小包括自己的vptr,vbptr,以及由vbptr指向的父类的大小;普通继承,派生类大小包括父类的数据成员以及自己的vptr
多重继承

对一个多重派生对象,将其地址指定给最左端base class的指针,情况将和单一继承时相同,因为二者都指向相同的起始地址,需付出的成本只有地址的指定操作而已。至于第二个或后继的base class的地址指定操作,则需要将地址修改过:加上介于中间的base class subobject大小。
注意:存取第二个(或后继)base class中的一个data member,不需要额外的成本,member的位置在编译时就固定了。不管是经由指针、引用还是object来存取
class X {
public:
static void fun() {
printf("%d\n", &X::a);
printf("%d\n", &X::b);
printf("%d\n", &X::m);
printf("%d\n", &X::n);
printf("%d\n", &X::x);
printf("%d\n", &X::y);
}
int a;
int b;
protected:
int m;
int n;
private:
int x;
int y;
};
int main()
{
X::fun();
system("pause");
return ;
}
输出为0,4,8,12,16,20
取某个成员的地址,表示该成员在class object中的偏移量,c++要求同一个access level中的members在内存中的排列次序应该和其声明次序相同,vptr在vs2013中应该是放到对象的尾部了。
参考:
http://blog.csdn.net/littlehedgehog/article/details/5442430
http://blog.csdn.net/hyg0811/article/details/11951855#
http://blog.csdn.net/wangqiulin123456/article/details/8059536
【深入探索c++对象模型】data语义学二的更多相关文章
- 《深度探索C++对象模型》读书笔记(一)
前言 今年中下旬就要找工作了,我计划从现在就开始准备一些面试中会问到的基础知识,包括C++.操作系统.计算机网络.算法和数据结构等.C++就先从这本<深度探索C++对象模型>开始.不同于& ...
- Data 语义学(1)
一.Data Member 的绑定(The binding of Data Member) extern float x; class Point3d { public: Point3d( float ...
- 拾遗与填坑《深度探索C++对象模型》3.2节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
- [读书系列] 深度探索C++对象模型 初读
2012年底-2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起<深度探索C++对象模型>这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio ...
- 深入探索C++对象模型(三)
Data 语义学 一个class的data members,一般而言,可以表现这个class在程序执行时的某种状态.Nonstatic data members放置的是"个别的class o ...
- 拾遗与填坑《深度探索C++对象模型》3.3节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
- 深度探索C++对象模型
深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...
- 深入探索C++对象模型(一)
再读<深入探索C++对象模型>笔记. 关于对象 C++在加入封装后(只含有数据成员和普通成员函数)的布局成本增加了多少? 答案是并没有增加布局成本.就像C struct一样,memeber ...
- c++学习书籍推荐《深度探索C++对象模型》下载
百度云及其他网盘下载地址:点我 百度云及其他网盘下载地址:点我 编辑推荐 如果你是一位C++程序员,渴望对于底层知识获得一个完整的了解,那么这本<深度探索C++对象模型>正适合你 作者简介 ...
随机推荐
- CAD交互绘制样条线(网页版)
在CAD设计时,需要绘制样条线,用户可以设置样条线线重及颜色等属性. 主要用到函数说明: _DMxDrawX::SendStringToExecuteFun 把命令当着函数执行,可以传参数.详细说明如 ...
- C-基础:关于预编译以及宏
这是没有引入任何头文件时,如果使用"NULL",编译器会报错:没有定义NULL.此时可用下面代码定义. #undef NULL //#undef 是在后面取消以前定义的宏定义#if ...
- 搜索 || DFS || POJ 2488 A Knight's Journey
给一个矩形棋盘,每次走日字,问能否不重复的走完棋盘的每个点,并将路径按字典序输出 *解法:按字典序输出路径,因此方向向量的数组按字典序写顺序,dfs+回溯,注意flag退出递归的判断,并且用pre记录 ...
- git-忽略文件改动不进行提交
命令:git update-index --assume-unchanged 文件名 作用:忽略文件的改动,但是不加入.gitignore 文件中,这样可以达到仅在本地目录中忽略,不影响其他团队成员的 ...
- IFE春季班第一阶段任务(请仔细阅读)
第一阶段的主要目标是帮助大家 了解.认识.学习.掌握HTML及CSS.第一阶段任务从 3月14日 开始,持续到 4月3日.当然,您也可以在这个时间以后继续自行实践练习. 第一阶段任务一共有 12 个题 ...
- luogu P1455 搭配购买
题目描述 明天就是母亲节了,电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢?听说在某个网站上有卖云朵的,小朋友们决定一同前往去看看这种神奇的商品,这个店里有n朵云,云朵已经 ...
- 洛谷 P2337 【[SCOI2012]喵星人的入侵】
这几天一直在刷插头Dp,写了几道入门题后,觉得还比较水,直到我发现了这一题.... 题目大意:给你一个n*m的地图,有些是空地,有些是障碍,还有两个是ST,在给你一个L,代表可以放L个炮台,你要在空地 ...
- Hibernate的核心配置
Hibernate的设计思路 Hibernate是一种全自动化管理持久化对象的ORM框架,既提供了完全面向对象的封装完整的对象持久化接口(屏蔽db层的差异化,提升代码可移植性),也提供了操作HQL和S ...
- PHP中的预定义常量、类常量和魔术常量的区别
PHP 向它运行的任何脚本提供了大量的预定义常量.不过很多常量都是由不同的扩展库定义的,只有在加载了这些扩展库时才会出现,或者动态加载后,或者在编译时已经包括进去了. 对于一些基本的常量是这些常量在 ...
- 导出csv文件(php实现)
<?php namespace App\Library\lib; class CsvLib { /** * [构造函数] * */ public function __construct() { ...