View C++ as a federation of languages.
C,Object-Oriented C++,Template C++,STL
Prefer consts, enums and inlines to #defines.
对于单纯变量,最好以const对象或enum替代#defines;
对于形似函数的宏(micros),最好改用inline函数替换#defines。
Use const whenever possible.
将某些东西声明为const可帮助编译器侦测出错误用法,const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体;
编译器强制实施bitwise constness,但是编写程序的时候应该使用”概念上的常量“;
当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
Make sure that objects are initialized before they are used.
{C++规定,对象成员变量的初始化发生在进入构造函数本体之前,通常使用成员初始化列表比在构造函数中赋值效率更高。如果成员变量是const或者references,就必须使用初始化列表,不能进行赋值。C++有固定的成员初始化次序,base class早于derived class被初始化,class的成员变量以声明的次序被初始化。}
为内置类型对象进行手工初始化,因为C++不保证初始化它们;
构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在class中的声明次序相同;
为免除”跨编译单元之初始化次序“问题,请以local static对象替换non-local static对象。
Know what functions C++ silently writes and calls.
编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符以及析构函数。
Explicitly disallow the use of compiler-generated functions you do not want.
//阻止copy构造函数和copy assignment的调用
class Uncopyable{
protected:
Uncopyable(){}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale:private Uncopyable {
};
为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。
Declare destructors virtual in polymorphic base classes.
{C++中,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,执行的结果未定义。}
带多态性质的base class应该声明一个virtual析构函数,如果class带有任何virtual函数,它就应该拥有一个virtual析构函数;
class的设计目的如果不是作为base class使用,或不是为了具备多态性,就不应该声明virtual析构函数。
Prevent exceptions from leaving destructors.
析构函数不要抛出任何异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后不传播任何异常或者结束程序;
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数,而不是在析构函数中执行该操作。
Never call virtual functions during construction or destruction.
在构造和析构期间不要调用virtual函数,因为这类调用不会下降到derived class。
Have assignment operators return a reference to *this.
令赋值操作符返回一个reference to *this。
Handle assignment to self in operator=.
确保对象自我复制时operator=有良好行为,其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy and swap;
确保任何函数如果操作一个以上的对象,而其中多个对象是同一对象时,其行为仍然正确。
Copy all parts of an object.
{复制所有local成员变量,调用所有base classes内适当的copying函数(copy构造函数以及operator=操作符)。令copy assignment操作符调用copy构造函数是不合理的,试图构造一个已经存在的对象,C++中没有相关的语法。令copy构造函数调用copy assignment操作符同样没有意义,构造函数用于初始化新对象,而assignment操作符只用于已初始化的对象。}
Copying函数应该确保复制“对象内的所有成员变量”以及“所有base class”成分;
不要尝试以某个copying函数实现另一个copying函数,可将共同的代码部分放进第三个函数中,并由两个copying函数共同调用。
Use objects to manage resources.
{使用auto_ptr,析构函数自动对其所指对象调用delete,auto_ptr会自动删除所指的对象,所以不能让多个auto_ptr同时指向同一对象,如果通过copy构造函数或copy assignment操作符复制auto_ptr,原有的auto_ptr会变成null,复制所得的指针获得资源的唯一拥有权。}
为防止资源泄漏,应使用RAII对象(RAII(Resource acquisition is initialization)资源获取即初始化),它们在构造函数中获得资源并在析构函数中释放资源;
两个常被使用的RAII classes分别是shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观,若选择auto_ptr,复制动作会导致其指向null。
Think carefully about copying behavior in resource-managing classes.
复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为;
普遍而常见的RAII class copying行为是:抑制copying,施行引用计数法。
Provide access to raw resources in resource-managing classes.
APIs往往要求访问原始资源,所以每个RAII class应该提供一个“取得其所管理资源”的办法;
对原始资源的访问可能经由隐式转换或显式转换,一般而言显式转换比较安全,但隐式转换对客户比较方便。
Use the same form in corresponding uses of new and delete.
如果在new表达式中使用[ ],必须在相应的delete表达式中使用[ ]。如果在new表达式中不使用[ ],一定不要在相应的delete表达式中使用[ ]。
Store newed objects in smart pointers in standalone statements.
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
//-----------------------------------------------------------------
str::tr1::shared_ptr<Widget> pw(new Widget); //独立语句
processWidget(pw, priority());
以独立语句将newed对象存储入智能指针中,否杂可能会有难以察觉的异常抛出。
Make interfaces easy to use correctly and hard to use incorrectly.
class Month{
public:
static Month Jan() {return new Month(1);}
static Month Feb() {return new Month(2);}
private:
explicit Month(int m);
};
好的接口很容易被正确使用,不容易被误用,应该在所有的接口中努力达成这些性质;
“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容;
“阻止误用”的办法包括建立新类型、限制类型上的操作、束缚对象值、以及消除客户的资源管理责任;
tr1::shared_ptr支持定制型删除器,这可防范DLL问题,可被用来自动解除互斥锁。
Treat class design as type design.
class的设计就是type的设计。
Prefer pass-by-reference-to-const to pass-by-value.
class Window {
public:
string name() const;
virtual void display() const;
};
class WindowBar:public Window {
public:
virtual void display() const;
};
void printWindow(Window w) {
cout << w.name() << endl;
w.display();
}
//可能存在参数切割现象
//WindowBar wb;
//printWindow(wb);
//通过传引用的方式就会存在参数切割
void printWindow(const Window& w);
尽量以pass-by-reference-to-const替换pass-by-value,前者比较高效,并可避免参数切割问题;
以上规则并不适用于内置类型,以及STL的迭代器和函数对象,pass-by-value会比较合适。
Don't try to return a reference when you must return an object.
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象。
Declare data members private.
切记将成员变量声明为private,这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性;
protected并不比public更具封装性。
Prefer non-member non-friend functions to member functions.
{提供较大封装性的是non-member-non-friend函数,因为它并不增加能够访问“class的private充分”的函数数量。将所有便利函数放在多个头文件内但隶属同一个命名空间,意味客户可以轻松扩展这一组便利函数。}
宁可拿non-member non-friend函数替换member函数,这样做可以增加封装性、包裹弹性和机能扩充性。
Declare non-member functions when type conversions should apply to all parameters.
如果需要为某个函数的所有参数进行类型转换,那么这个函数必须是non-member。
Consider support for a non-throwing swap.
{不允许改变std命名空间内的任何东西,但可以为标准templates(如swap)制造特化版本,使它专属于某一个class。}
#include <iostream>
#include <string>
using namespace std;
class mystring{
public:
mystring(char *s) {
str = s;
}
string str;
};
namespace std{
template<> // std::swap的全特化版本,针对mystring
void swap<mystring>(mystring &a, mystring& b) {
cout << "template mystring" << endl;
}
}
int main() {
mystring a("aaaa");
mystring b("bbbb");
swap(a, b);
cout << a.str << endl << b.str << endl;
system("pause");
return 0;
}
当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常;
如果你提供一个member swap,也该提供一个non-member swap来调用前者。对于class(而非templates),也请特化std::swap;
调用swap时应该针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”;
为“用户定义类型”进行std templates全特化是好的,但是不要尝试在std内加入某些对std而言全新的东西。
Postpone variable definitions as long as possible.
//default construct + copy
string ms;
ms=pass;
//copy construct,效率更高
string ms(pass);
尽可能延后变量定义式的出现,可避免不必要的构造和析构调用,增加程序的清晰度并改善程序效率。
Minimize casting.
{const_cast dynamic_cast reinterpret_cast static_cast}
如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast,如果有个设计需要转型动作,试着发展无需转型的替代设计;
如果转型是必要的,试着将它隐藏于某个函数背后,客户可以调用该函数,而不需将转型放进代码内;
宁可使用C++ style转型,不要使用旧式转型。
Avoid returning "handles" to object internals.
避免返回handles(包括references、指针、迭代器)指向对象内部,遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生dangling handles的可能性降至最低。
Strive for exception-safe code.
异常安全函数即使发生异常也不会泄露资源或允许任何数据结构被破坏,这样的函数区分为三种可能的保证:基本型,强烈型,不抛异常型;
“强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义;
函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。
Understand the ins and outs of inlining.
{过度inline会造成程序体积太大,导致额外的换页行为,降低指令高速缓存的击中率}
将大多数inline限制在小型、被频繁调用的的函数上,这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化;
不要只因为function templates出现在头文件,就将它们声明为inline。
Minimize compilation dependencies between files.
支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式,基于此构想的两个手段是Handles class和Interface class;
程序库头文件应该以“完全且仅有声明式”的形式存在,不论是否涉及templates都适用。
Make sure public inheritance models "is-a".
“public继承”意味is-a,适用于base-class身上的每一件事情一定也适用于derived-class,因为每一个derived-class对象也是一个base-class对象。
Avoid hiding inherited names.
derived class内的名称会遮掩base-class内的名称;
为避免上述情况,可以适用using声明式或转交函数。
Differentiate between inheritance of interface and inheritance of implementation.
接口继承和实现继承不同,在public继承之下,derived class总是继承base class的接口;
pure virtual函数只具体制定接口继承;
impure virtual函数具体指定接口继承及缺省实现继承;
non-virtual函数具体指定接口继承以及强制性实现继承。
Consider alternatives to virtual functions.
virtual函数的替代方案包括NVI手法以及Strategy设计模式的多种形式,NVI手法自身是一个特殊形式的Template Method设计模式;
将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员;
tr1::function对象的行为类似一般的函数指针。
Never redefine an inherited non-virtual function.
{任何情况下都不应该重定义一个继承而来的non-virtual函数}
绝对不要重新定义继承而来的non-virtual函数
Never redefine a function's inherited default parameter value.
{virtual函数式动态绑定,但是函数缺省参数值是静态绑定}
class Shape {
public:
virtual void draw(int color = 0) const = 0;
};
class Rectangle:public Shape {
public:
virtual void draw(int color = 1) const {
cout << color << endl;
}
};
class Circle:public Shape {
public:
virtual void draw(int color) const {
cout << color << endl;
}
};
int main() {
Shape *ptr = new Circle;
ptr->draw(); //输出0
ptr = new Rectangle;
ptr->draw(); //输出0
system("pause");
return 0;
}
绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,但是virtual函数是动态绑定的。
Model "has-a" or "is-implemented-in-terms-of" through composition.
复合的意义和public继承完全不同;
在应用域,复合意味has-a,在实现域,复合意味is-implemented-in-terms-of(根据某物实现)。
Use private inheritance judiciously.
{private继承不代表is-a关系。}
private继承意味is-implemented-in-terms-of(根据某物实现),通常比复合的级别低;
和复合不同,private继承可以造成empty base最优化。
Use multiple inheritance judiciously.
多重继承比单一继承复杂,可能导致新的歧义性,以及对virtual继承的需要;
virtual继承会增加大小,速度,初始化复杂度等成本,如果virtual base class不带任何数据,将是最具实用价值的情况;
多重继承有正当用途,其中一个涉及“public继承某个Interface class”和“private 继承某个协助实现的class”的组合。
Understand implicit interfaces and compile-time polymorphism.
{以不同template参数具现化“function template”会导致调用不同的函数,这就是编译时期多肽。}
class和template都支持接口和多肽;
对class而言接口是显式的,以函数签名为中心,多肽则是通过virtual函数发生于运行期;
对template参数而言,接口是隐式的,基于有效表达式,多肽则是通过template具现化和函数重载解析发生于编译期。
Understand the meanings of typename.
{任何时候想在template中指涉一个嵌套从属类型名称,就必须在紧邻的前一个位置写上typename。}
template <typename C>
void printTest(const C& c) {
if (c.size() >= 2) {
C::const_iterator iter(c.begin()); // error
// typename C::const_iterator iter(c.begin());
int value = *iter;
cout << value << endl;
}
}
int main() {
string s("abcd");
printTest(s);
system("pause");
return 0;
}
声明template参数时,前缀关键字typename和class可互换;
使用关键字typename标示嵌套从属类型名称,但不得在base class lists或成员初始值列内以它作为base class修饰符。
Know how to access names in templatized base classes.
可在derived class templates内通过“this->”指涉base class templates内的成员名称,或借助一个明白写出的“base class资格修饰符”完成。
Factor parameter-independent code out of templates.
Templates生成多个class和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系;
因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数;
因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述具现类型实现共享码。
Use member function templates to accept "all compatible types".
使用member function templates生成“可接受所有兼容类型”的函数;
如果生命member templates用于“泛化copy构造”或“泛化assignment操作”,依然需要声明正常的copy构造函数和copy assignment操作符。
Define non-member functions inside templates when type conversions are desired.
当我们编写一个class template,而它所提供之“与此template相关的函数”支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
Use traits classes for information about types.
“Traits classes”使得类型相关信息在编译期可用,它们以“templates”和“templates特化”完成实现;
整合重载技术后,traits classes有可能在编译期对类型执行if else 操作。
Be aware of template metaprogramming.
模板元编程可将工作由运行期转向编译期,因而得以实现早期错误检测和更高的执行效率。
Understand the behavior of the new-handler.
set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
Understand when it makes sense to replace new and delete.
有很多理由需要写个自定的new和delete,包括改善效能、对heap运用错误进行调试、收集heap使用信息。
Adhere to conversion when writing new and delete.
operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就应该调用new-handler,它也应该有能力处理0 bytes申请,class专属版本还应该处理“比正确大小更大的(错误)申请”;
operator delete应该在收到null指针时不做任何事,class专属版本还应该处理“比正确大小更大的(错误)申请”。
Write placement delete if you write placement new.
当你写一个place operator new,请确定也写出了对应的placement delete,否则可能会发生内存泄露;
当声明placement new和placement delete,确定不要无意识地遮掩了正确的版本。
Pay attention to compiler warnings.
正确对待编译器发出的警告信息。
Familiarize yourself with the standard library, including TR1.
C++标准程序库的主要机能由STL、iostreams、locals组成;
TR1添加了智能指针、一般化函数指针、hash-based容器、正则表达式以及另外10个组件的支持。
Familiarize yourself with Boost.