单一继承中,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语义学二的更多相关文章

  1. 《深度探索C++对象模型》读书笔记(一)

    前言 今年中下旬就要找工作了,我计划从现在就开始准备一些面试中会问到的基础知识,包括C++.操作系统.计算机网络.算法和数据结构等.C++就先从这本<深度探索C++对象模型>开始.不同于& ...

  2. Data 语义学(1)

    一.Data Member 的绑定(The binding of Data Member) extern float x; class Point3d { public: Point3d( float ...

  3. 拾遗与填坑《深度探索C++对象模型》3.2节

    <深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...

  4. [读书系列] 深度探索C++对象模型 初读

    2012年底-2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起<深度探索C++对象模型>这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio ...

  5. 深入探索C++对象模型(三)

    Data 语义学 一个class的data members,一般而言,可以表现这个class在程序执行时的某种状态.Nonstatic data members放置的是"个别的class o ...

  6. 拾遗与填坑《深度探索C++对象模型》3.3节

    <深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...

  7. 深度探索C++对象模型

    深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...

  8. 深入探索C++对象模型(一)

    再读<深入探索C++对象模型>笔记. 关于对象 C++在加入封装后(只含有数据成员和普通成员函数)的布局成本增加了多少? 答案是并没有增加布局成本.就像C struct一样,memeber ...

  9. c++学习书籍推荐《深度探索C++对象模型》下载

    百度云及其他网盘下载地址:点我 百度云及其他网盘下载地址:点我 编辑推荐 如果你是一位C++程序员,渴望对于底层知识获得一个完整的了解,那么这本<深度探索C++对象模型>正适合你 作者简介 ...

随机推荐

  1. Comparator.comparing比较排序

    使用外部比较器Comparator进行排序 当我们需要对集合的元素进行排序的时候,可以使用java.util.Comparator 创建一个比较器来进行排序.Comparator接口同样也是一个函数式 ...

  2. java web开发中常用的协议的使用和java-web 常见的缓冲技术

    一.DNS协议 作用将域名解析为IP   类似于我们只需要知道中央一台,中央二台,而不需要知道它的频率,方便记忆. java dns 域名解析协议实现 1 域名解析,将域名可转换为ip地址InetAd ...

  3. 把特征网络换成resnet-50

    从RFCN来看,Resnet-50和Resnet-101到最后一层卷积都是缩小到原来尺寸的16分之一,并且都用的7x7的格子去roi pooling. 看paper可以知道:resnet-50核心是由 ...

  4. 筛选法 || POJ 3292 Semi-prime H-numbers

    5,9,13,……叫H-prime 一个数能且仅能由两个H-prime相乘得到,则为H-semi-prime 问1-n中的H-semi-prime有多少个 *解法:vis初始化为0代表H-prime, ...

  5. Python List extend()方法

    Python List extend()方法  Python 列表 描述 extend() 函数用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表). 语法 extend()方法语法 ...

  6. 任务一:零基础HTML编码

    面向人群: 零基础或初学者 难度: 简单 重要说明 百度前端技术学院的课程任务是由百度前端工程师专为对前端不同掌握程度的同学设计.我们尽力保证课程内容的质量以及学习难度的合理性,但即使如此,真正决定课 ...

  7. vue 运行时 + 编译器 vs. 只包含运行时

    https://cn.vuejs.org/v2/guide/installation.html#运行时-编译器-vs-只包含运行时 文档中的这个地方,说的不清楚 If you need to comp ...

  8. python re 正则表达式

    元字符和其含义 . 匹配除换行符以外的任意字符 \ 转义字符,使后一个字符改变原来的意思 \w 匹配字母.数字.下划线:[A-Za-z0-9_] \W 匹配特殊字符:[^A-Za-z0-9_] \s ...

  9. LeetCode(14)Longest Common Prefix

    题目 Write a function to find the longest common prefix string amongst an array of strings. 分析 该题目是求一个 ...

  10. ES6(Iterator 和 for...of 循环)

    Iterator 和 for...of 循环 1.什么是 Iterator 接口 Iterator 接口功能:用一种相同办法的接口让不同的数据结构得到统一的读取命令的方式 2.Iterator的基本用 ...