一、基础 Basics
条款1 指针(pointer)与引用(reference)的选择
1.pointer可以为空,但reference不能
因此对于一个变量如果总是代表一个对象,则应该该考虑为reference
且使用reference不需要测试其有效性,效率更高。使用pointer前需判断是否为空
2.pointer可以重新被赋值,但reference总是代表初始化的对象
当存在不指向任何对象或者指向对象有变化时应该使用pointer
3.在实现操作符重载是,常常返回类型为reference,可以作为左值
条款2 使用c++转型操作符
4个新的转型操作符(cast operators):static_cast,const_cast,dynamic_cast,reinterpret_cast
写法为: static_cast<type>(expression)
注意在static_cast<vector<int> >
最后两个>
最好有空格,防止编译器认为是>>
操作符 | 作用 | 备注 |
---|---|---|
static_cast |
用于基本的类型转换 | 不涉及继承机制 |
const_cast |
用于改变表达式的常量属性,去掉const性质 | |
dynamic_cast |
用于执行继承体系中安全向下或者跨系转型动作(将指向base的指针转换成指向derived) | 对于pointer,失败返回null,对于reference,失败返回exception;只能协助巡航在继承体系中,无法应用在缺乏虚函数的类型上 |
reinterpret_cast |
最常用于转换“函数指针” | 与编译平台有关,不具有移植性 |
条款3 不以多态方式处理数组
对于数组array
,数组名也为首地址。array[i]
=== *(array+i)
array
与array+i
内存之间的距离一定是i*sizeof(item)
编译器对于数组中对象的大小在声明时定下,导致多态时是无法正确定位实际的内存距离
条款4 非必要不提供默认构造函数
不提供默认构造函数会带来一下三点的不便
- 不能产生类对象的数组(解决方法:1,non-heap数组,提前给定初始化的变量 2指针数组 3,
placement new
方式) - 不适用于基于模板的容器类,因为被实例化的目标类型需要一个默认构造函数
- 虚基类不提供默认构造函数,派生类必须提供初始化参数
然而,如果含有无意义成员变量的对象存在如果可以生存,那么对于大部分的成员函数都必须检测该成员变量,降低效率。
如果类的构造函数能够保证对象的所有字段都被正确初始哈,那么这些测试代码、对于的处理程序所带来的时间和空间的代价可以免除,
显然,默认构造函数无法带来这样的保证,那么最好避免让默认构造函数出现。
二、操作符 Operators
条款5 对定制的“类型转化函数”保持警觉
当存在以下条件之一,便有可能发生隐式类型转换
- 含有单自变量构造函数。能以单一变量成功初始化对象(利用关键词
explicit
阻止隐式转换,或采用proxy class) - 成员函数含有隐式类型操作函数。如 operator double()const;
最好不提供任何类型转换函数,问题在于此类函数的调用可能超出你的预期,结果可能是不正确,不直观并且难以调试。
例子:
标准库程序string类并未含有**从string对象到C风格char*的隐式转换函数 **,而是需要显示调用c_str
成员函数来执行这种转化行为
条款6 自增自减操作符的前置与后置
自增自减的重载,为了区别前置与后置只好让后置有一个int形参,编译器默认指定为0
1 | class UPInt{ |
前置:increment and fetch 累加然后取出,返回值是引用
后置:fetch and increment 取出然后累加,返回值是一个const常量(防止i++++出现,毫无意义)
尽可能使用前置式 原因在于
- 后置式会产生临时变量
- 后置式一般是以前置式为基础上实现的
条款7 不要重载&&
,||
,,
操作符
c++对于真假表达式采用骤死式的评估方式。一旦该表达式的真假值确定后,剩余部分将不再进行计算。
即使c++允许我们重载,但是函数调用式会取代骤死式,这有悖游戏规则
如将&&
重载,
1 | if(expr1 && expr2) |
操作符重载的目的是让程序更容易被阅读,被撰写,被理解,而不是炫技或者夸耀
条款8 了解new
与delete
三、 异常Exceptions
条款9 利用析构函数避免泄漏资源
实质上就是采用智能指针,不必担心内存泄漏
把资源(一般是指针)封装在对象中(如智能指针这种对象),利用这种对象的析构函数避免资源泄露
条款10 在构造函数内组织资源泄露
虽然可以在析构函数中进行资源释放,然而如果异常发生在对象的构造阶段,便不可行。
原因是c++只会析构已构造完成的对象,这种机理的理由在于避免额外开销:如果需要析构未完成构造的对象,必然要记录下构造函数进行到什么程度,这势必会造成额外开销。
对于指针成员变量,一律采用智能指针可有效的避免资源泄露