这是《More Effective C++》一书的读书笔记。

基础

指针用于需要在不同时间指向不同对象(包含空对象)的情形,而引用则是确定指向某一对
象并不再更改。引用代价比指针代价要小,因为它不需要判断是否为空。函数参数的时候,
引用尽量使用const进行修饰。

C++中风格转换包括四种,其中staic_cast是最常用的,而const_cast用于去除
const修饰,dynamic_cast用于将一个基类指针转换为子类指针(有时候,基类指针指
向的是一个子类对象实现多态,对于编译器,它是不知道这个指针所指向的对象的,即通过
该指针,我们无法调用子类单独定义的函数,这个时候,需要告诉编译器这实际上是一个子
类指针),reinterpret_cast用于函数指针间的转换(用的比较少,不了解)。

不要对数组使用多态,因为对数组进行遍历的时候,它每次的偏移值大小是基类对象的大小
,但如果包含了多态,就会导致错误,因为这个时候应该偏移子类对象的大小,但事实上却
只是基类对象的大小。所以不要用过基类指针来调用delete []basePtr

运算符

隐式类型转换是很危险的,经常会导致一些莫名其妙的结果。在构造函数中,使用一个不带
explicit的单参数(或者多参数,但第二个之后的参数都有默认值)会导致隐式类型转换
。而使用operator int()等会导致向整型的隐式转换,想想STL中string类都是用
c_str来返回它的一个C类型的字符串,而不是用operator char* (),所以我们在写代
码的时候,尽量不要用这类的类型重载函数。

前缀形式和后缀形式如i++++i,其意义是很明显的。对于前后缀自增减的重载,是通
过参数来决定的,operator++ ()表示的是前缀自增重载,而operator++ (int)则表示
后缀自增重载,另外,如果是自定义的类型,很有可能前缀会效率更高,因为它返回的是一
个引用,而后缀返回的是一个常量对象。

一般情况下,也不会想着(也千万不要)去重载一些莫名其妙的运算符,如&&等。

对于new,了解多少呢?重载new操作符,我们可以改变默认的内存分配方式,一般情况
下,operator new只分配内存,不涉及到构造函数。通常所见的new包含了分配内存+构
造函数初始化。如果想在指定的位置生成对象,可以用new (address) class(parameter)
,它不会生成新的内存,而是在给定的内存地址上生成对象。

异常

不要在析构函数中抛出异常。在C++中,调用delete操作或者抛出异常导致堆栈的unwinding
过程都会导致析构函数的自动调用。如果一个异常被激活的同时,析构函数也抛出异常的话
,就会导致程序的控制权转移到析构函数之外,C++调用terminate终止函数,立即终止函数
的运行,可能连局部对象都不会释放。

TODO:对异常不了解,以后再细看

效率

数据结构和算法的改进当然是可以大幅度地提高时空效率,但好的实现也是可以提高程序的
效率的,好的实现前提就是对C++的了解。

根据80-20准则,20%的程序消耗着80%的运行时间或空间,所以找出这些性能热点
并集中进行改进,才是最有意义的。这里提到了延迟计算,它是提高性能的一个重要方法,
因为很多情况下,我们复制元素后,实际上并不需要对这个复制的元素进行更改,这个时候
可以用指针或引用计数之类的方法来避免深度拷贝,等到真的需要更改才进行深度拷贝。

编译器返回值优化是一个很重要的手段。此外,虚函数和多态等RTTI也是会带来时间开销的
,具体可以参考《C++对象模型》一书。

随着继承体系的复杂度增加,效率也会有所下降,因为虚指针需要不断去链接找到真实的函
数指针。 这后面的部分还需要再看

技巧