条款1:指针与引用的区别
二者之间的区别是:在任何情况下都不能用指向空值的引用,而指针则可以;指针可以被重新赋值以指向另一个不同的对象,但是引用则总是指向在初始化时被指定的对象,以后不能改变。
在以下情况下使用指针:一是存在不指向任何对象的可能性;二是需要能够在不同的时刻指向不同的对象。
在以下情况使用引用:总是指向一个对象且一旦指向一个对象之后就不会改变指向;重载某个操作符时,使用指针会造成语义误解。
条款2:尽量使用C++风格的类型转换
static_cast:功能上基本上与C风格的类型转换一样强大,含义也一样。但是不能把struct转换成int类型或者把double类型转换成指针类型。另外,它不能从表达式中去除const属性。
const_cast:用于类型转换掉表达式的const或volatileness属性。但是不能用它来完成修改这两个属性之外的事情。
dynamic_cast:用于安详地沿着类的继承关系向下类型转换。失败的转换将返回空指针或者抛出异常。
reinterpret_cast:这个操作符被用于的类型转换的转换结果时实现时定义。因此,使用它的代码很难移植。最普通的用途就是在函数指针之间进行转换。
条款3:不要使用多态性数组
多态和指针算法不能混合在一起使用,所以数组和多态也不能用在一起。
数组中各元素的内存地址是数组的起始地址加上之前各个元素的大小得到的,如果各元素大小不一,那么编译器将不能正确地定位元素,从而产生错误。
条款4:避免无意义的缺省构造函数
没有缺省构造函数造成的问题:通常不可能建立对象数组,对于使用非堆数组,可以在定义时提供必要的参数。另一种方法是使用指针数组,但是必须删除数组里的每个指针指向的对象,而且还增加了内存分配量。
提供无意义的缺省构造函数会影响类的工作效率,成员函数必须测试所有的部分是否都被正确的初始化。
条款5:谨慎定义类型转换函数
缺省的隐式转换将带来出乎意料的结果,因此应该尽量消除,使用显式转换函数。通过不声明运算符的方法,可以克服隐式类型转换运算符的缺点,通过使用explicit关键字和代理类的方法可以消除单参数构造函数造成的隐式转换。
条款6:自增和自减操作符前缀形式与后缀形式的区别
后缀式有一个int类型参数,当函数被调用时,编译器传递一个0作为int参数的值传递给该函数。可以在定义时省略掉不想使用的参数名称,以避免警告信息。
后缀式返回const对象,原因是 :使该类的行为和int一致,而int不允许连续两次自增后缀运算;连续两次运算实际只增一次,和直觉不符。
前缀比后缀效率更高,因为后缀要返回对象,而前缀只返回引用。另外,可以用前缀来实现后缀,以方便维护。
条款7:不要重载&&,||,或者“,”
对 于以上操作符来说,计算的顺序是从左到右,返回最右边表达式的值。如果重载的话,不能保证其计算顺序和基本类型想同。操作符重载的目的是使程序更容易阅 读,书写和理解,而不是来迷惑其他人。如果没有一个好理由重载操作符,就不要重载。而对于&&,||和“,”,软件开发,很难找到一个好理由。
条款8:理解各种不同含义的new和delete
new操作符完成的功能分两部分:第一部分是分配足够的内存以便容纳所需类型的对象;第二部分是它调用构造函数初始化内存中的对象。new操作符总是做这两件事,我们不能以任何方式改变它的行为。
我们能改变的是如何为对象分配内存。new操作符通过调用operator new来完成必需的内存分配,可以重写或重载这个函数来改变它的行为。可以显式调用operator来分配原始内存。
如果已经分配了内存,需要以此内存来构造对象,可以使用placement new,其调用形式为new(void* buffer)class(int size)。
对于delete来说,软件开发,应该和new保持一致,怎样分配内存,就应该采用相应的措施释放内存。
operator new[]与operator delete[]和new与delete相类似。
条款9:使用析构函数防止资源泄漏
使用指针时,如果在delete指针之前产生异常,将会导致不能删除指针,从而产生资源泄漏。
解决措施:使用对象封装资源,如使用auto_ptr,使得资源能够自动被释放。
条款10:在构造函数中防止资源泄漏
类中存在指针时,在构造函数中需要考虑出现异常的情况:异常将导致以前初始化的其它指针成员不能删除,从而产生资源泄漏。解决措施是在构造函数中考虑异常处理,产生异常时释放已分配的资源。最好的方法是使用对象封装资源。
条款11:禁止异常信息传递到析构函数外
禁止异常传递到析构函数外的两个原因:第一能够在异常传递的堆栈辗转开解的过程中,防止terminate被调用;第二它能帮手确保析构函数总能完成我们希望它做的所有事情。
解决方法是在析构函数中使用try-catch块屏蔽所有异常。
条款12:理解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”间的差异
有 三个主要区别:第一,异常对象在传递时总被进行拷贝。当通过传值方式捕获时,异常对象被拷贝了两次。对象作为参数传递给函数时不需要被拷贝;第二,对象作 为异常被抛出与作为参数传递给函数相比,前者类型转换比后者少(前者只有两种转换形式:继承类与基类的转换,类型化指针到无类型指针的转换);最后一点, catch子句进行异常类型匹配的顺序是它们在源代码中出现的顺序,第一个类型匹配成功的擦他处将被用来执行。当一个对象调用一个虚函数时,被选择的函数 位于与对象类 10f9 匹配最佳的类里,急事该类不是在源代码的最前头。
条款13:通过引用捕获异常
有三个选择可以捕获异常:第一、指 针,建立在堆中的对象必需删除,而对于不是建立在堆中的对象,删除它会造成不可预测的后果,因此将面临一个难题:对象建立在堆中还是不在堆中;第二、传 值,异常对象被抛出时系统将对异常对象拷贝两次,而且它会产生“对象切割”,即派生类的异常对象被作为基类异常对象捕获时,它的派生类行为就被切割调了。 这样产生的对象实际上是基类对象;第三、引用,完美解决以上问题。
条款14:审慎使用异常规格
避免调用unexpected函数 的措施:第一、避免在带有类型参数的模板内使用异常规格。因为我们没有措施知道某种模板类型参数抛出什么样的异常,所以不可能为一个模板提供一个有意义的 异常规格;第二、如果在一个函数内调用其它没有异常规格的函数时应该去除这个函数的异常规格;第三、处理系统本身抛出的异常。可以将所有的 unexpected异常都被替换为自定义的异常对象,或者替换unexpected函数,使其重新抛出当前异常,这样异常将被替换为 bad_exception,从而代替原来的异常继续传递。
很容易写出违反异常规格的代码,所以应该审慎使用异常规格。