# ======
# 让自己习惯C++
# ======
# 第一条 (1)c++高效编程守则视状况而变化,取决于你使用c++的哪一部分。
1. C
2. Object-Oriented C++
3. Template C++
4. STL
# 第二条 (1)对于单纯常量,最好以const对象或enums替换#define;对于形似函数的宏,最好改用inline函数替换#define。
1. #define 报错时可能不会提到你用到的宏定义,而是宏定义的内容,因此你可能有一些迷惑。
2. 取枚举的地址是不合法的,因此不想让别人获取整形常量的地址或者指针,类内可以用枚举实现,也不会导致非必要的内存分配。
# 第三条 (1)将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体;编译器强制实施bitwise(const成员函数无法修改非静态成员变量,可用mutable强制修改),但你编写程序时应该使用“概念上的常量性”(对对象而言可接受,但是编译器不接受);(2)当const和non-const成员函数有着实质等价的实现时,领non-const版本调用const版本可避免代码重复。
1. const vector<int>::iterator iter:无法改变迭代器,但是可以改变迭代器指向的值。vector<int>::iterator iter:无法改变迭代器指向的值,但是可以改变迭代器。
2. 成员函数的const属性可以进行重载。
3. operator[]的返回值是&,因此可以对其进行赋值修改,如果不是&,则赋值修改的只是其一个副本。
4. mutable声明的非静态成员变量,可以无惧bitwise constness约束,在const成员函数中进行修改。
5. 同样功能的同名函数:const成员函数可以被非const成员函数调用,进而减少代码重复,但是非const成员函数不能被const成员函数调用,因为const成员不对成员变量进行修改,这样违背了函数设计的原则。
# 第四条 (1)为内置型对象进行手工初始化,因为C++不保证初始化它们;(2)构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和他们在class中的声明次序相同;(3)为免除“跨编译单元之初始化次序”问题,晴以local static对象替换non-local static对象。
1. 无参数构造函数
ABEntry::ABEntry()
:theName(),theAddress(),thePhones(),numTimesConsulted(0){}
内置类型一定要初始化,不然很容易开启“不明确行为”的潘多拉盒子。
2. 不同文件内的non-local static变量初始化顺序是不明确的,因此使用未初始化的变量可能会导致问题。解决:将non-local static对象搬到函数内,进行函数的调用,保证首次遇到该对象时进行初始化,这是Singleton模式的一个常见手法。这种函数也比较使用声明为inline函数。
# ======
# 构造/析构/赋值运算
# ======
# 第五条 (1)编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。
1. 类中含有引用的成员变量,必须自己定义拷贝赋值操作符,因为C++不允许让reference指向不同对象,以及不允许该reference被修改,影响其他持有该reference指向的对象。同理const成员操作符也是一样的。
2. base class的拷贝赋值函数声明为private,则编译器无法为derived class生成默认的copy assignment操作符。
# 第六条 (1)为驳回编译器自动提供的功能,可将相应的成员函数声明为private并且不予实现。使用Uncopyable这样的base class也是一种拒绝派生类被copy的做法。
# 第七条 (1)带多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数;(2)Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。
1. derived对象由base class指针删除,而base class带着一个non-virtual析构函数,其执行通常是对象的derived成分没有被销毁。一个不用做base class的类不应该被声明为虚析构函数,会导致对象体积增大以及不具备较好的移植性。
2. 纯虚函数是可以定义缺省实现的,需要一个abstract class作为借口,可以定义其纯虚析构函数,并给出缺省实现。
# 第八条 (1)析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序;(2)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
# 第九条 (1)在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)
1. 可以通过在derived class 中构造函数的初始化列表中调用父类构造函数(构造子类时候显式调用父类构造函数),传递信息。
2. class Transaction{
3. public:
explict Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const;
...
};
Transaction::Transaction(const std::string& logInfo)
{
...;
logTransaction(logInfo);
}
class BuyTransaction : public Transaction {
public:
BuyTransaction(parameters):Transaction(createLogString(parameters))//初始化列表调用父类构造函数
{...}
private:
static std::string createLogString(parameters);//静态函数
}
# 第十条 (1)令赋值操作符返回一个reference to *this
1. 为了赋值后的变量成为左值,仍然可以接受再次赋值。(a=b)=c 失败;a=b=c 成功;
# 第十一条 (1)确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址(判断地址是否相同)、精心周到的语句顺序(delete后移)、以及copy and swap;确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
# 第十二条 (1)Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”;(2)不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。
# ======
# 资源管理
# ======
# 第十三条 (1)为防止资源泄漏,请使用RAII对象,它们再构造函数中获得资源并再析构函数中释放资源;(2)两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向nullptr。
1. auto_ptr shared_ptr两者都在其析构函数中做delete而不是delete[],因此再动态分配的array身上使用这两个智能指针是一个馊主意。
# 第十四条 (1)复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为(自己编写);(2)普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。
1. shared_ptr可以指定删除器,即引用计数为0进行的操作
class Lock{
public:
explict Lock(Mutex* pm):mutexPtr(pm,unlock)
{
lock(mutexPtr.get());
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr;
}
# 第十五条 (1)APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理之资源”的办法;(2)对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便(可能造成意外的事情)。
1. shared_ptr auto_ptr都提供一个get成员函数,用来执行显式转换,返回智能指针内部的原始指针。其也重载了->和*可以访问指针内容。
2. 隐式转换:
class Font{
public:
...
operator FontHandle() const
{return f;}
private:
FontHandle f;
}
# 第十六条 (1)如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要再相应的delete表达式中使用[]。
# 第十七条 (1)以独立语句将newed对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。
1. processWidget(std::rt1::shared_ptr<Widget>(new Widget),priority),因为执行顺序的不同,可能导致内存泄漏。1 new Widget 2 priority 3 tr1::shared_ptr构造函数。
# ======
# 设计与声明
# ======
# 第十八条 (1)好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质;“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容;(2)“阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任;tr1::shared_ptr支持定制型删除器。这可防范DLL问题,可被用来自动解除互斥锁等等。
# 第十九条 (1)Class的设计就是type的设计。在定义一个新type之前,晴确定你已经考虑过本条款所付款的所有讨论主题。
# 第二十条 (1)尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题;(2)以上规则并不适用于内置类型ÿ