《Effective C++》重点摘要(二)

《Effective C++》第二章:构造/析构/赋值运算

  1. C++默认编写的函数。C++编译器如果没有发现以下函数,就会为类生成一份默认版本的:
    1) default构造函数
    2) default析构函数
    3) copy构造函数
    4) copy assignment操作符(=运算符)
    前两个函数并不总是产生,它只在编译器需要的时候才产生出来。后两个函数只保证以bitwise语义拷贝non-static成员,所以如果有指针、引用等non-static成员的类要么拒绝编译器产生的版本,要么自行小心实现这两个函数。
  2. 若不想使用编译器自动生成的函数,就明确拒绝之。关键在于需要明确正在编写的类是否允许这些默认函数的存在,如果确定不允许,那么,在C++98中可以将其声明为private访问权限的,而C++11中还可以将其声明为deleted。最好的做法是做一个通用的拒绝了编译器生成默认函数的基类,让不需要默认函数的类继承自它。
  3. 为多态基类声明virtual析构函数。如果基类的析构函数不是virtual的,那么基类的virtual table中不会有实际需要调用的析构函数,那么删除一个指向derived类指针的基类指针时,结果自然是不能确定的(通常是析构不完全)。
  4. 别让异常逃离析构函数(如归还资源)。如果析构函数中抛出了异常而没有被处理,那么析构函数该做的事情就很可能没做完,即便这个被它传播出来的异常被上层函数捕获并处理也无法为为抛出异常的对象做完它该做的事情。所以如果虚构函数中出现异常,要么abort程序,要么自行处理异常(析构函数吞下异常)。abort掉程序往往让人“心中一跳“,所以尽管“吞下异常”并不完美,也该如此。具体做法是为可能抛出异常的操作编写一个函数,并把这个函数暴露给客户,给客户一个自己处理异常的机会。如果客户没有自己处理,那么就在析构函数里替用户做这件事情。
  5. 绝不要在构造和析构过程中调用virtual函数。构造函数总是先调用基类的构造函数,所以在构造函数里调用虚函数在语义上总是不合理的,因为此时的虚函数一点都不“虚”,调用的是父类版本,析构函数情况也是一样,尽管总是后调用基类的析构函数,不,应该说,正是因为这样的调用顺序,使得虚函数“不虚”。
  6. 令operator=返回一个reference to *this。这样做,可以让对象能够做到连续赋值,让你设计的类更像一个内置type。这样返回一个引用是可行,因为*this不是一个局部变量,当离开operator=函数作用域时,*this不会被析构。
  7. 在operator=中处理“自我赋值”。编写operator=有四个基本要求:返回reference to *this,以const reference作为参数传递方式,确保深浅拷贝语义合适、处理自我赋值,即处理客户让一个对象=本身的操作,在编译器眼里这是合法的表达式,但是运行时有可能出错,解决之道是在copy之间先判断出入参数(是个引用)所指对象的地址和this是否相等。还有一个更高的要求是保证“异常安全”,这通常可以通过copy-and-swap实现。
  8. 复制对象时,勿忘其每一部分。这个有什么好说的,如果没有做到,就不是复制!!注意拷贝成员变量时需要的拷贝语义(深、浅拷贝),也不要忘了所有基类里的成员。也不要让拷贝构造函数和copy assignment函数相互调用,写一个共用的copy函数,让拷贝构造函数和copy assignment函数调用它。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。