《深度探索C++对象模型》读书笔记
第一章 关于对象
要点:
1. 对象模型
对象模型主要是讲了数据成员和函数在对象中的分布。
- 对于类来说,主要的数据成员分为 static 和 non static, 成员函数分为 static、non static 、virtual。
- 主要介绍了三类对象模型:
- 简单对象模型
- 表格驱动对象模型
- c++对象模型 --> 实际使用模型
- 类对象内存大小的计算
- nonstatic data members的总和大小
- 内存对齐
- 为支持virtual机制而产生的内存。
遗留的问题
1)p9 关于虚表指针和虚表的存储位置 类中还是对象中?
2)计算一下,问题中的几个特殊类的大小,并研究一下
3)虚函数表的第一个成员 type_info object 了解一下
2. class和struct的区别——其实没啥区别
3. 多态的实现,虚函数机制的讲解
- p25 c++支持多态的三种方式
- 基类指针或引用 指向派生类
- 虚函数
- RTTI(runtime type identification):dynamic_cast 和typeid运算符
- 指针或引用 与 对象的差别
- 同时指出了一个很关键的实现,指针和引用的类型决定了编译器如何去解析指针或引用所对应的空间。
原话是这么说的: 被指出之内存的大小和其内容的解释方式。
第二章 构造函数语意学
2.1 default constructor 的构造操作
这一节主要是讲编译器在什么情况下会合成一个默认构造函数。
-
类中含有带default constructor 的成员
这里有两种情况:-
其一,class A 没有默认构造函数,其成员对象class B有,这是编译器会合成一个默认的构造函数,构造函数可能就是简单的调用一下class B的构造函数,A中其它没有构造函数的成员,也不会去初始化。
合成的构造函数:
-
其二,class A 有默认构造函数,但是没有去调用 其成员class B(有构造函数的成员)的构造函数,这时,A的构造函数就会被扩充,顺序的调用A中有构造函数类的成员
)
扩充的构造函数:
-
-
含有带 default constructor 的 base class
派生类没有定义构造函数,基类带有构造函数,这时会合成一个,如果有默认构造函数但是没有调用基类的构造函数,这时会扩充现有的构造函数,同理上一条 -
带有虚函数的class
不管是自带虚函数还是继承的,或是一条继承链中含有virtual base classes。都要合成一个默认的constructor,还会扩充虚函数指针和虚函数表,constructor要去完成虚函数表指针的初始化,并为其设定正确的值。ps: 这里也表明了为什么构造函数不能是虚函数,因为虚表指针都是构造函数进行初始化的。
-
带有一个virtual base class的class
虚基类的作用,就是在多继承中,只保留一份共同的基类的成员。
编译器需要定义一个构造函数,去完成一个指针的初始化,此指针是指向virtual base class
总结:这一节主要是讲了会生成默认构造函数的4中情况。有以下几点注意的:
- 在合成的构造函数中,只有 base class subobjects 和 member class objects会被初始化。其他 non static data member 不带构造函数的是不会被初始化的。
- 编译器合成构造函数的原则就是满足编译器的需求,而非程序的需求。
- 错误:任何class如果没有定义default constructor,就会被合成出一个来。
- 错误:编译器合成出来的default constructor会显式设定“class内每一个data member的默认值”
2.2 copy constructor 的构造操作
这一节主要讲拷贝构造函数的合成,什么情况下需要合成拷贝构造函数,同时介绍了深拷贝(memberwise)和浅拷贝(bitwise)。
- 使用拷贝构造函数的三种情况
- 以一个对象作为另一个对象的初值
- 以对象作为函数的参数
- 以对象作为函数返回值
- 什么情况下合成拷贝构造函数
和默认构造函数一样,分为nontrivial和trivial两种。是否会有一个被编译器合成的实例被调用,就要看class是否展示了bitwise copy semantics。所谓bitwise copy semantics就是看浅拷贝是否可以满足要求,要是可以,就不会去合成拷贝构造函数。
有四种情况class不展示bitwise copy semantics,P53
- class 类有member object有copy constructor不论是合成的还是自定义的。
- class继承的base class 存在copy constructor
- 当class声明了一个或多个虚函数
- 当class派生自一个继承串链,其中有一个或多个虚基类
前两种要把已有的copy constructor加进来所以需要合成。
后面的两种涉及到虚指针,在虚指针需要重设的情况下,浅拷贝就不适用,其实拷贝就不适用,因为新的虚指针和原来的不一样了。
2.3 程序转化语意学
主要是关于copy constructor的优化的问题,在对象作为参数和返回值的情况下。
2.4 成员初始化列表(member initialization list)
- 四种必须使用初始化列表的情况 p75
- 初始化引用成员
- 初始化const成员
- 当要调用基类的构造函数,并且构造函数还有输入参数
- 当要初始化一个成员,此成员是一个类对象,要调用此类的构造函数,并且构造函数还有输入参数
-
成员初始化列表可以提高效率
贴三张图,其实就是为了表示 A a(0);效率比 A a = 0;高
-
成员初始化列表有陷阱 —— 初始化的顺序
注意点:
- 初始化列表的赋值顺序由成员在类中的声明顺序决定,而不是由列表的顺序决定。
- 初始化列表的执行在构造函数体中其他语句执行之前
- 在初始化列表中使用成员函数可能语法没有问题,但是顺序可能会导致一些问题。
第三章 data语意学
这一章主要讲的是类中data member的内存分布,还有单继承,多继承,虚函数,多态,对效率的影响。不是很感兴趣,可能是因为水平还不够,对于是否要使用继承,使用几层继承,对c++使用效率,以及空间的影响。
值得注意的:
- 空类的大小为1,为什么? --> 要是定义为0,对于这样的情况 A a,b; 则 a和b的地址就是一样的了。
- 多重继承中data的内存分布
- 虚继承中派生类中是还有基类的数据,外加基类的指针
- p105 p106这两个图特别有意思
其他的没有印象, 尴尬。。
第四章 fuction语意学
放着,感觉看这一章的时候快睡着了。
第五章 构造、析构、拷贝语意学
这一章还比较有意思,可能是带着疑问去读的吧,各种面试题都会考到的东西,在这里讲的比较清楚。
5.0 前言
- 纯虚函数可以有函数体,析构函数不要定义为纯虚函数。虚函数最好不用const。
5.1 “无继承”情况下的对象构造
-
全局变量的构造和析构的时机 —— 构造在 main()调用之前,析构在exit退出时。
这里可以看一下这篇文章 程序真的是从main开始的吗? - CSDN博客
-
默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符 —— 定义和差别,以及编译器只有在需要的时候才会去合成,什么时候需要和第二章的情况相似。
拷贝构造函数 —— 用一个已有的对象,去创建一个新的对象
拷贝赋值运算符 —— 用一个已有的对象,去给另一个已有的对象赋值
5.2 继承体现下的对象构造
- p206 构造函数的扩充
- 构造函数和析构函数中调用虚函数 —— 这时,不会用到虚函数机制,就是静态调用本类的函数。为什么呢,简单的说,这是派生类对象还没实例化出来,所以可能会找不到对应的函数执行。
- 虚函数指针的初始化时机 —— 基类构造函数调用后,本类的构造函数调用的最前面,在成员初始化列表前。
- 在成员初始化列表中调用本类的虚函数安全吗?安全,因为虚函数指针已经初始化完成。但是不推荐,因为函数可能依赖某些还未初始化的成员变量。
- 在需要给基类构造函数传一个参数(就是要调用基类带参数的构造函数),此时在该类的构造函数中调用虚函数 安全吗? 不安全。怎么讲,就是此虚函数可能会是指向本类的虚函数,此时虚函数使用的任何成员变量一定还没初始化。因为基类的虚指针要么还没初始化,要么就是已经初始化了,就是指向本类的虚函数。本来的成员初始化,是在此成员初始化列表之后的。
5.3 对象复制语意学
- 拷贝赋值运算符 不会表现出bitwise copy的情况 p220, 这是编译器回去合成拷贝赋值运算符
5.4 对象的效能
没怎么看,比较效率的,重要但是目前级别体会不到
5.5 析构函数语意学
- 析构函数在什么情况下会被合成
- 析构函数的扩展 p234
第六章 执行器语意学
6.1 对象的构造和析构
定义了一个对象,什么时候会自动调用其构造函数,什么时候去调用析构函数
- 全局变量
- 局部静态变量 ——只会初始化一次,但是可能会用到很多次,所以什么时候去构造和析构(在程序起来的时候,就把所有的static变量构造出来,等程序结束时析构)
- 数组 —— 和new delete那样的,是否会去调用每个元素的构造函数和析构函数,对于只初始化部分元素的呢?
6.2 new和delete
new和delete,作用于数组的情况,new的重载
6.3 临时性对象
- 编译器什么时候会去创建临时对象?
- named return value优化
- 临时对象析构的时机,以及两种延时析构的情况(临时对象作用给了引用,临时对象要留到表达式执行完并正确初始化或赋值给另一对象)
- 临时对象的产生会很影响编译器的效率
第七章 站在对象模型的尖端
7.1 template
没仔细看,看的没有意思 这一章
- template 什么时候实例化,是否会实例化
- template中的名称决议法(没怎么看懂,尴尬)
- member fuction的实例化
7.2 异常处理
7.3 执行期类型识别(runtime type identification, rtti)
- downcast(向下转换): 基类转派生类
- 安全的downcast : dynamic_cast
这个是通过type_info这个类型描述器来确认对象的类型的,其原理是记录对象的实际类型名称。存放在虚函数表的第一槽中
以下是stl中type_info的定义:
class type_info {
public:
virtual ~type_info();
bool operator==(const type_info& rhs) const noexcept;
bool operator!=(const type_info& rhs) const noexcept;
bool before(const type_info& rhs) const noexcept;
size_t hash_code() const noexcept;
const char* name() const noexcept; //类型名
type_info(const type_info& rhs) = delete; // 不能复制
type_info& operator=(const type_info& rhs) = delete; // 不能复制
};
- typeid运算符:这个返回的就是type_info的引用。
后记
终于快速的过了一遍,这本书放在床头快半年了。是一本不错的书,看完对于构造,析构,还有虚函数机制从编译器的角度有了一定的了解。对于一些问题有了更清晰的认识,而不是像以前只有表面认识,如 构造函数析构函数中调用虚函数,构造函数能不能是虚函数,为什么析构函数一般定义为虚函数,拷贝构造函数,拷贝赋值运算符,默认构造函数 等等。收获还是很多的。
对于书中关于效率的部分没怎么仔细看,目前等级理解不到。