文章目录

对象大小

  1. 语言本身所造成的额外负担,如虚基类
  2. 编译器对于特殊情况的优化处理,如某些编译器对空虚基类的特殊支持,一般放在头部 就省去 1 byte 大小的空间
  3. 字节对齐

数据成员的绑定

早期的两种防御性编程:

  1. 把所有的 data members 放在 class 声明头处确保正确的绑定
  2. 把所有的 inline functions, 不管大小定义都放在 class 声明之外

C++ 2.0 之后这种风格消失了,因为对其成员函数的分析会到整个 class 的声明都出现才 开始。

数据成员的布局

C++ 标准要求同一个 access section 中, members 的排列顺序只需符合 较晚出现的 members 在 class object 中有较高的地址, 也就是说不一定连续。 members 之间可能会 存在一些 padding 或着合成的 data members, 如 vptr.

当前编译器都是把多个 access sections 按照声明的顺序连锁在一起成为连续区。

数据成员的存取

静态数据成员

只有一个实体,存放在数据段。存取时会内部转化为唯一的 extern 实体。

非静态数据成员

存在类对象中,只能通过显式或隐式(如this)的类对象进行存取。

存取非静态数据,编译器需要把类对象起始地址加上数据成员的偏移。

继承与数据成员

派生类的数据表现出的东西是自己和继承的成员的总和,但是排列次序,C++标准并未规定; 大部分编译器中,基类先出现。但 虚基类 除外。

只要继承不要多态

容易犯的错误:

  1. 重复设计一些相同操作的函数
  2. 把 class 分解多蹭导致所需空间膨胀

加上多态

空间和存取时间的额外负担:

  1. 类的虚函数表 virtual table
  2. 类对象的虚函数指针 vptr
  3. 加强构造函数,能够设置 vptr 初始值指向类的虚函数表
  4. 加强析构函数,抹消 vptr

vptr 应放在什么位置,因不同的编译器而不同:

  1. 前端:多重继承时,通过指向类成员的指针调用虚函数会有优势;但丧失可 C 语言的兼 容性
  2. 后端:保留 C struct 的对象布局

多重继承

派生类对象将其地址指定最左边(第一个)基类的指针,情况和单一继承一样,因为有相同 的起始地址。

第二个及后继基类的地址指定操作则需要将地址修改,加上或减去介于中间的基类的大小。

虚继承

副作用:必须支持某种形式的 shared suobject 继承 语意。所表现的是基类子对象的数 据的位置会因每次的派生操作而变化;所以只能间接存取,存取策略因不同的编译器而不同。

指向数据成员的指针

需要详细调查成员低层布局时比较有用,可以决定 vptr 是在起始处还是尾端。

  1. 静态数据成员取地址获得内存地址
  2. 非静态数据成员取地址获得的是相对于对象起始地址的偏移。

要输出成员的地址要用 %p 格式化输出,不能用 std::cout 。