MoreEffectiveC++(一)

一、基础 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)

arrayarray+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
2
3
4
5
6
7
8
9
10
11
12
13
class UPInt{
UPInt& operator++(); //前置++
const UPInt operator++(int); //后置++
};
UPInt& UPInt::operator++(){
*this +=1; //累加
return *this; //取出
}
const UPInt UPInt::operator++(int){
UPInt oldValue = *this; //取出
++(*this); //累加
return oldValue;
}

前置:increment and fetch 累加然后取出,返回值是引用

后置:fetch and increment 取出然后累加,返回值是一个const常量(防止i++++出现,毫无意义)

尽可能使用前置式 原因在于

  • 后置式会产生临时变量
  • 后置式一般是以前置式为基础上实现的

条款7 不要重载&&,||,,操作符

c++对于真假表达式采用骤死式的评估方式。一旦该表达式的真假值确定后,剩余部分将不再进行计算。

即使c++允许我们重载,但是函数调用式会取代骤死式,这有悖游戏规则

如将&&重载,

1
2
3
4
5
6
7
if(expr1 && expr2)
;
//实际编译是下列两者之一
if(expr1.operator&&(expr2)) //成员函数形式
;
if(operator&&(expr1,expr2)) //全局函数形式
;

操作符重载的目的是让程序更容易被阅读,被撰写,被理解,而不是炫技或者夸耀

条款8 了解newdelete

三、 异常Exceptions

条款9 利用析构函数避免泄漏资源

实质上就是采用智能指针,不必担心内存泄漏

把资源(一般是指针)封装在对象中(如智能指针这种对象),利用这种对象的析构函数避免资源泄露

条款10 在构造函数内组织资源泄露

虽然可以在析构函数中进行资源释放,然而如果异常发生在对象的构造阶段,便不可行。

原因是c++只会析构已构造完成的对象,这种机理的理由在于避免额外开销:如果需要析构未完成构造的对象,必然要记录下构造函数进行到什么程度,这势必会造成额外开销。

对于指针成员变量,一律采用智能指针可有效的避免资源泄露