深度探索C++对象模型笔记--第一章
第一章. 关于对象
1.0 加上封装后的布局成本
-
一个类的成员函数不包含在obj中, 非内联函数只产生一个实例, 内联函数则是在被使用到的模块中产生实例.
-
虚函数 : 用于支持动态绑定.
-
虚基类 : 用于支持 在一个继承体系中多次出现的基类有一个单一且被共享的实例.
1.1 C++对象模型
-
非静态成员变量置于每一个类对象中.
-
静态成员变量, 静态 / 非静态成员函数置于类对象外.
-
类中的每一个虚函数都有一个对应的指针, 并且这些指针置于一张表格中, 这张表格被称为virtual table ( 虚表 / vtbl), 此外, 每一个类对象所关联的type_info(用于支持RTTI)也被放置于vtbl中, 通常位于表格的首位.
-
C++内存区域 : 堆, 栈, 静态存储段, 常量段, 程序段.
-
每一个类对象中会生成一个指向该类中vtbl的指针 virtual pointer ( 虚指针 / vptr), vptr的设置和重置都由类的 构造,析构,拷贝赋值 运算来自动完成.
-
对于这种模型, 如果应用程序代码本身未曾改变, 但所用到的类对象的非静态成员变量有变动 (增加, 移除 或 更改), 那么应用程序代码则需要重新编译.
-
虚继承 : 无论基类在继承链中被派生多少次, 永远只会存在一个实例 ( 子对象 / subobject ).
1.2 关键词所带来的差异
-
对于C++代码来说, 何时应该选用struct, 何时选用class? ( 当你觉得用struct更舒服的时候 ).
-
由于兼容C代码, 在下述情况下, 应该选用struct而不是class, C++中凡处于同一个访问域的数据, 是必定能保证以其声明顺序出现在内存布局中的, 但被放置在多个访问域中的各个变量, 其在内存布局中排列的顺序是不确定的.
struct mumble
{
/*stuff*/;
char pc[ 1 ]; //用来获取变长字符串
}
const char *ss = "HelloWorld";
//code
struct mumble *pmumb1 = ( struct mumble* )malloc( sizeof( struct mumble ) + strlen( ss ) + 1);
strcpy( pmumb1->pc, ss ); -
把C和C++结合在一起的唯一可行方法是组合 : 当要传递一个复杂的类对象到一个C函数中的时候, struct可以将数据封装起来, 并保证拥有与C兼容的空间布局.
1.3 对象所带来的差异
-
只有通过指针或引用来处理继承体系中的基类对象, 才支持面向对象模型程序设计所需的多态性质.
-
C++中用于支持多态的方法: (1) 隐式类型转换; (2) 虚函数; (3) dynamic_cast和typeid运算符.
-
多态的主要用途是经由一个共同的接口来影响类型的封装, 接口通常被定义在一个抽象的基类中.
-
一般而言, 定义一个类对象的内存大小需要: (1) 非静态成员变量内存大小总和; (2) 字节对齐填充的pad; (3) 编译器为了支持virtual而添加的内容.
-
不管指针指向哪一种数据类型, 其本身所需要的内存大小是固定的.
-
指针所指的数据类型, 会指导编译器如何解释某个特定地址中的内存内容及其大小.
-
类型转换(cast)可以理解为一种编译器指令, 大部分情况下它并不改变指针所含的真正地址, 而是影响 所指内存的大小和其内容 的解释方式.
-
一个指向派生类对象的基类指针只能直接处理基类中的成员变量, 唯一能处理派生类成员的方法是通过virtual机制.
-
一个指针的类型将在编译期间决定该指针固定可用的接口和该接口的访问等级(public, protected, private).
-
编译器必须确保如果一个对象中含有一个及以上的vptr, 这些vptr的内容不会被基类对象 初始化/修改.
-
指针或引用由于多态所造成的改变并不包括所该类型(对编译器而言)的内存大小, 改变的是所指内存的大小和内容的解释方式.
-