让自己习惯C++
条款01:视C++为一个语言联邦(View C++ as a federation of languages)
C++是个多重范型编程语言(multiparadigm programming language),一个同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。C++相关的次语言主要包括四个:C、Object-Oriented C++、Template C++和STL。
1)C++高效编程守则视状况而变化,取决于你使用C++的哪个部分。
条款02:尽量以const,enum,inline替换#define(prefer consts,enums,and inlines to #defines.)
1)class专属常量,通常为了确保class专属常量至多只有一份实体,你必须让它成为一个static成员。class专属常量通常需要一个放进实现文件的定义式。
2)enum hack的行为某个方面说比较像#define而不像const,例如取一个const的地址是合法的,而取一个enum的地址是不合法,而取一个#define的地址通常也不合法。
3)对于单纯常量,最好以const对象或enums替换#define。
4)对于形式函数的宏,最好改用inline函数替换#defines。
条款03:尽可能使用const(Use const whenever possible)
关键字const多才多艺,可以用它在classes外部修饰global或namespace作用域中的常量,或修饰文件、函数、或区块作用域中被声明为static的对象,也可以用它来修饰classes内部的static和non-static成员变量。面对指针,也可以指出指针自身、指针所指物,或两者都(或都不)是const。
1)const成员函数,将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。
2)两个成员函数如果只是常量性(constness)不同,可以被重载。
3)将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
4)bitwise const(又称physical constness):成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const,也就是说它不更改对象内的任何一个bit。
5)logical constness:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。
6)编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)
7)当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
条款04:确定对象被使用前已先被初始化(Make sure that objects are initialized before they’re used.)
1)为内置型对象进行手工初始化,因为C++不保证初始化它们。
2)构造函数最好使用成员初始化列表,(member initialization list),而不要在构造函数本体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
3)所谓编译单元是指产出单一目标文件的那些源码,基本上它是单一源码文件加上其所含入的头文件(#include files)。
4)以免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。
构造/析构/赋值运算
条款05:了解C++默默编写并调用哪些函数(Know what functions C++ silently writes and calls.)
当C++处理class类,如果你自己没声明,编译器就会为它声明(编译器版本的)一个copy构造函数、一个copy assignment操作符和一个析构函数。此外如果你没有声明任何构造函数,编译器也会为你声明一个default构造函数。所有这些函数都是public且inline。
唯有当这些函数被需求(被调用),它们才会被编译器创建出来。
1)default构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,像是调用base classes和non-static成员变量的构造函数和析构函数。注意,编译器产出的析构函数是个non-virtual。
2)至于copy构造函数和copy assignment操作符,编译器创建的版本只是单纯地将来源对象的每一个non-static成员变量拷贝到目标对象。
条款06:若不想使用编译器自动生成的函数,就该明确拒绝(Explicitly disallow the use of compiler-generated functions you do not want.)
1)将copy构造函数和copy assignment操作符声明为private并且故意不实现它们,可以有效阻止人们调用它们。
条款07:为多态基类声明virtual析构函数(Declare destructors virtual in polymorphic base classes.)
1)对于virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数该被调用。这份信息通常是由一个所谓vptr(virtual table pointer)指针指出。vptr指向一个由函数指针构成的数组,称为vtbl(virtual table),每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl——编译器在其中寻找适当的函数指针。
2)只有当class内含至少一个virtual函数,才为它声明virtual析构函数。
3)polymorphic(带有多态性质的)base classes应该声明一个virtual析构函数。
4)classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。
条款08:别让异常逃离析构函数(Prevent exceptions from leaving destructors.)
1)C++并不禁止析构函数吐出异常,但它不鼓励你这样做。
2)析构函数绝对不要吐出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
3)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
条款09:绝不在构造和析构过程中调用virtual函数(Never call virtual functions during construction or destruction.),因为这类调用从不下降至derived class)
1)在base class构造期间,virtual函数不是virtual函数。
条款10:令operator=返回一个reference to *this (Have assignment operators return a reference to *this.)
条款11:在operator=中处理“自我赋值”(Handle assignment to self in operator=.)
在operator=最前面加一个“证同测试(identity test)”达到“自我赋值”的验证目的:
1)Widget& Widget::operator=(const Widget& rhs)
{
if( this == &rhs) return *this; //证同测试,如果是自我赋值,就不做任何事
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
2)Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
如果“new Bitmap”抛出异常,pb(及其栖身的那个Widget)保持原状。
3)copy and swap技术
class Widget{
...
void swap(Widget& rhs); //交换*this和rhs的数据
...
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); //为rhs数据制作一份复件(副本)
swap(temp); //将*this数据和上述复件的数据交换
return *this;
}
4)确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
5)确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
条款12:复制对象时勿忘其每一个成分(Copy all parts of an object.)
1)当编写一个copying函数,请确保(1)复制所有local成员变量;(2)调用所有base classes内的适当的copying函数。
2)不要尝试以某个copying函数实现另一个copying函数,应该将共同机能放进第三个函数中,并由两个copying函数共同调用。