C++对象模型学习笔记3 Data语意学

Data 语意学

  • {P83} 若干类的继承关系如下:
         class X{};
         class Y : public virtual X{};
         class Z : public virtual X{};
         class A: public Y, public Z{};
  1. class X, 一个empty class,占用1 byte,是被编译器安插进去的一个char,目的是为了一个class的两个objects得以在内存中拥有独一无二的地址,以区分不同的对象。

  2. {P85} Y和Z的大小受到三个因素的影响:
    1. 语言本身所造成的额外负担overhead。语言支持virtual base classes时导致的额外负担反映在某种形式的指针身上,它要么指向virtual base class subobject,要么指向一个存放virtual base class subobject地址或者其偏移量offset的表格。
    2. 编译器对于特殊情况所提供的优化处理。virtual base class X 1 byte大小的subobject也出现在class Y和Z身上。
    3. Alignment的限制。Alignment就是将数值调整到某数的整数倍。
  3. 一个virtual base class subobject只会在derived class中存在一份实体,不管它在class继承体系中出现了多少次。

C++ standard并不强制规定base class subobjects、不同存取级别的data members的排列次序这种琐碎细节,它也不规定virtual function以及virtual base classes的实现细节。


static vs. non-static {P87-88}
  • C++对象模型尽量以空间优化和存取速度优化来表现nonstatic data members,并且保持和C语言struct数据配置的兼容性。它把数据直接存放在每一个class object中,对于继承而来的nonstatic data members,不管是virtual或nonvirtual base class也是如此。
  • 至于static data members则被放置在程序的一个global data segment中,不会影响个别class object的大小。static data member永远只存在一份实体,但是一个template class的static data member的行为稍有不同。

Data Member的绑定 {P89-91}
  • 对于class member function本体的分析,会在整个class声明完成后才发生。所以我们不用担心引用的x是class成员x或者是外部的x,古老的C++需要担心这个问题,于是成员声明都写在最开头。
  • 而对于成员函数argument list中的名称还是会在它们第一次遭遇时被适当地决议resolved完成。基于这种状况,请始终把nested type声明放在class的起始处。

Data Member的布局
  • {P92} C++ Standard要求,在同一个access section中,members的排列只需满足“较晚出现的members在class object中有较高的地址”这一条件即可。也就是说各个members并不一定的连续排列,alignment可能需要的bytes以及编译器可能合成供内部使用的data members都可能介于被声明的members之间。

  • {P93} C++ Standard也允许编译器将多个access sections之中的data members自由排列,不必在乎它们出现在class声明中的次序。

  • {P94} 判断哪一个section先出现的template function:

template <class class_type, class data_type1, class data_type2>
char* access_order(data_type1 class_type::*mem1,
                   data_type2 class_type::*mem2)
{
  assert(mem1 != mem2);
  return mem1 < mem2 ? 
      “member 1 occurs first” : “member 2 occurs first”;
}

access_order(&Point3D::z, &Point3D::y);

Data Member的存取
  • {P95} static data member只有一个实体,程序的data segment之中
  • {P97} 有多少个编译器,就有多少种name-mangling做法 really??? 任何name-mangling做法都有两个要点:
    1. 一种算法,推导出独一无二的名称;
    2. 如果编译系统或者环境工具必须和使用者交互,那些独一无二的名称可被轻易推导回原先的名称。
  • {P97} non-static data members直接放在每一个class object之中,除非经过显示的explicit或隐含的implicit class object,否则没有办法直接存取它们。implicit主要发生在成员函数中,其实质是编译器会增添一个const this指针,而在函数体内通过这个this指针来存取non-static data member

继承与Data Member
  • {P99} 在C++继承模型中,一个derived class object所表现出来的东西,是其自己的members加上其base classes members的总和。C++并未规定derived class members和base classes members的排列次序。不过,在大部分编译器上,除virtual base class外,base class members总是先出现。

  • 一般而言,具体继承(concrete inheritance 相对于虚拟继承) 并不会增加空间或存取时间上的额外负担。

  • 容易犯的错误: {P102} 把一个class分解为多层,有可能会为了表现class体系之抽象化而膨胀所需空间(因为编译器的边界调整)。其根本原因是C++保证出现在derived class中的base class subobject有其完整原样性。所以拆开后每个如果需要对齐,将会额外多出空间。如果不这么支持,从派生类到基类的转换将会出问题。

多态
  • {P111} 把vptr放在class object的尾端,可以保留base class C struct的对象布局,因而允许在C程序中也能使用。在C++问世之初被大量采用。
  • 把vptr放在class object的开始处,对于“在多重继承下,通过指向class members的指针调用virtual function,会带来一些帮助。否则,不仅“从class object 起始点开始量起”的offset必须在执行期备妥,甚至与class vptr之间的offset也必须备妥。当然,放在前端,代价就是丧失了C语言兼容性。
多重继承
  • 多重继承的复杂度在于derived class和其上一个base class乃至上上一个base class之间的“非自然关系” {P112},其主要问题发生在derived class objects和其第二或后继的base class objects之间的转换。{P113-115} 举个例子:
	class A {int x;};
	class A1 : public A {int y;};
	class B {int z;}
	class AB : public A1, B {int a;}
	AB ab;
	A* pa = &ab; 直接ab
	A1* pa1 = &ab; 直接ab
	B* pb = &ab; 非自然转换 &ab?(B*)((char*)&ab+sizeof(A1)):0; 
  • C++并未要求多重继承时derived class object中各个base class subjectes的排列次序,目前各个编译器都是根据声明次序来排列它们。
  • 这里对成员的访问无需额外成本,因为offset是固定的!
虚拟继承

{P117-P118} Class内如果内含一个或多个virtual bass class subobjects,像iostream那样,将被分割为两部分:一个不变区域和一个共享区域。不变区域的数据,不管后继怎么变化,总是拥有固定的offset,可以直接存取;而共享区域表现的就是virtual base class subobject,这一部分的数据,其位置会因为每次的派生操作而变化,只可间接存取。各家编译器实现技术之间的差异就在于间接存取的方法不同。
类继承关系
方式1 Pointer Strategy
方式2 Virtual Table Offset

一般而言,virtual base class最有效的一种运用方式是:一个没有任何data member的抽象class。


指向Data Members的指针

{P129} 一个有点神秘但颇有用处的语言特性,特别是如果你需要详细调查class members的底层布局。这样的调查可用来决定vptr是放在class的起始处还是尾端。另一个用途,还可用来决定class中access sections的次序。

printf("%p\n", &Point3d::x); // 4
printf("%p\n", &Point3d::y); // 8
printf("%p\n", &Point3d::z); // C ⇒ 这里存的是offset
如果x,y,z是连续的,而且是从0开始的,说明vptr在尾端。否则,vptr在前面。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值