1. 让自己习惯C++

条款01: 视C++为一个语言联邦

1.1 C++ 是一个多重泛型编程语言(multiparadigm programming),支持:过程形式(procedural),面向对象形式(object-oriented),函数形式(functional),泛型式(generic),元编程(metaprogramming)。

1.2 将C++看做主语言,那么他就四种次语言组成:C,object-oriented C++,Template C++, STL。

请记住:

C++ 高效编程守则视状况而变化,取决于你使用C++的哪一部分。

条款02: 尽量以const、enum、inline替换#define

2.1 用#define定义常量时,由于#define是preprocessor负责的,编译器看不到,所以编译出错时,会很麻烦。尽量以const取代之。

2.2 常量式通常被放在头文件中,以便被不同的源码引入。

2.3 #define无法定义类的常量成员,更不能提供封装,const就可以。类中的static const成员,不仅要在类内初始化,还要在类外定义。

  1.  
    //头文件
  2.  
    class A{
  3.  
    private:
  4.  
    static const int Num = 5;
  5.  
    int scores[Num];
  6.  
    }
  7.  
    //源文件
  8.  
    const int A::Num;

2.4 在2.3中提到,类内的static const成员要在类内初始化,如果编译器不允许类内初始化,可以用enum hack的做法,来变相实现。

  1.  
    class A{
  2.  
    private:
  3.  
    enum{ Num = 5 };
  4.  
    int scores[Num];
  5.  
    }

2.5 在C++11标准以前,只允许对类的static const integral成员做in-class initialization。在C++11之后,类的所有数据成员均可做类内初始化。另外,enum hack实际上利用enumeration不是类型安全的特点(弊端),这个特点在C++11中通过scoped enumeration来弥补,所以enumerator到整形的隐式转换是不提倡做的。故,在C++11下,需要用enum来替换#define的情况几乎不存在。

2.6当#define定义类似函数的功能时,带来了很多不安全因素,通常用内联函数(模板)代替。

  1.  
    #define MAX(a, b) (a)>(b)?(a):(b)
  2.  
    int a = 5, b = 0;
  3.  
    MAX(++a, b);//a被累加了两次

请记住:

1)对于单纯常量,最好以const对象或者enums替换掉#defines。

2)对于形似函数的宏(macros),最好用inline函数替换掉#defines。

条款03: 尽可能使用const。

3.1 在能用const的地方就使用const有利于编译器对对象进行处理,而且可以避免错误的使用。

3.2 作为成员函数,是否有const qualifier区别,可以被重载。

3.3 const成员函数的const的意义,有两大流行概念:bitwise constness(又称physical constness)和logical constness。

3.4 使用mutable可以释放掉non-static成员变量的bitwise constness约束。

3.5 当同时定义一个功能的const版本和nonconst版本时,注意代码重用:nonconst版本调用const版本,不要用反。

  1.  
    class A{
  2.  
    public:
  3.  
    const char& operator[](size_t index) const{
  4.  
    /*...*/
  5.  
    return text[index];
  6.  
    }
  7.  
    char& operator[](size_t index){
  8.  
    const_cast<char&>( static_cast<const A&>(*this)[index] );
  9.  
    }
  10.  
    };

请记住:

1)将某些东西声明为const可帮助编译器侦测出错用法,const被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体。

2)编译器强制实施bitwise constness,但我们在编写程序时应该使用logical constness。

3)当const合nonconst成员函数有着实质等价的实现时,令nonconst版本调用const版本可以避免代码重复。

条款04: 确定对象被使用前已被初始化

4.1 C++中default initialization和value initialization的区别。

4.2 类数据成员的初始化是发生在constructor body执行之前的(通过initialization list和in-class initializer),构造函数体内是赋值,不是初始化。

4.3 类的数据成员的初始化顺序总是固定的,若initialization list和in-class initializer都没有对某成员执行初始化,那么该成员执行default initialization。

4.4 注意在用new动态申请时new type()和new type的区别。

4.5 若object的lifetime从构造出来开始,一直到程序结束,那么就称之为static对象(未必被static修饰,如全局变量),包括global对象、定义于namespace作用域内的对象,以及在class内、函数内、文件作用域内的被声明为static的对象;他们都存放于静态存储区(或者称作全局区)。在函数内的static对象被称作local static对象(用static修饰),其余均是非local的。C++对于定义于不同编译单元的non-local static对象的初始化次序没有明确规定。当一个non-local static对象的初始化要依赖于另一个编译单元内的non-local static对象时,往往结果是不可预测的。解决办法,将非local static对象,放到函数内,即转为local static。这就是singleton模式的做法。

请记住:

1)为built-in对象手工初始化,因为C++不保证初始化。

2)构造函数最好使用成员初始化列表(member initialization list),而不要在构造函数体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在类中的声明次序相同。

3)为免除“跨编译单元之初始化次序”问题,请以local static对象代替non-local static对象。

2. 构造,析构,赋值运算

条款05: 了解C++默默编写并调用哪些函数

5.1 编译器合成的函数,只有在被需要(被调用)的时候才会被编译器创建出来。

请记住:

编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。补充:在C++11标准下,编译器还会合成move-copy构造函数和move-copy assignment操作符。

条款06: 若不想使用编译器自动生成的函数,就该明确拒绝

6.1 针对将成员函数声明为private并且不予实现的方法。补充:做了测试,发现不适用于非纯虚函数。当非纯虚函数没有定义时,只要执行构造函数,就会出现链接错误。

请记住:

为了驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明成private并且不予实现。使用像Uncopyable这样的base class也是一种做法。补充:在C++11中可以直接使用=delete限定符来明确驳回。

条款07: 为多态基类声明virtual析构函数

7.1 由于STL容器的析构函数都不是virtual的,所以我们不应该(不是不能)继承他们。

7.2 析构函数可以声明为纯虚函数,但必须定义。

请记住:

1)polymorphic(带多态性质的)base classes 应该声明一个virtual析构函数。如果class带有任何virtual函数,他就应该拥有一个virtual析构函数。

2)Classes的设计目的如果不是为了作为base classes使用,或不是为了具备多态性(polymorphically),就不应该声明virtual析构函数。

条款08: 别让异常逃离析构函数

请记住:

1)析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下他们(不传播)或结束程序。

2)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

条款09: 决不在构造和析构过程中调用virtual函数

9.1 间接调用也不可以。

请记住:

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。

条款10: 令operator= 返回一个reference to *this

10.1 由于右结合性,x=y=z=15等价于a=(y=(z=15))。

请记住:

令赋值(assignment)操作符返回一个reference to *this。

条款11: 在operator=中处理“自我赋值”

11.1 通过this==&val来判断是否是自赋值。

11.2 即使已经处理了自我赋值,operator=往往不具有“异常安全”性。异常安全可以通过精心安排的语句来保证,还可以使用copy and swap技术。C++11中还要考虑move assignment operator 情况。

  1.  
    A& A::operator=(A ai){
  2.  
    swap(ai);
  3.  
    return *this;
  4.  
    }

请记住:

1)确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。

2)确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

条款12: 复制对象时勿忘其每一个成分

请记住:

1)Copying函数应该确保复制“对象的所有成员变量”及所有base class成分。

2)不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

3. 资源管理

条款13: 以对象管理资源

13.1 以对象管理资源,即“资源取得时便是初始化时机”(Resource Acquisition Is Initialization;RAII),获得资源即用来初始化(或赋值)管理对象。

13.2 管理对象运用析构函数确保资源被释放。

请记住:

1)为防止资源泄露,请使用RAII对象,他们在构造函数中获得资源并在析构函数中释放资源。

2)两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。。。 。补充:在C++11中,提供了std::shared_ptr,std::unique_ptr和std::weak_ptr来管理资源。

条款14: 在资源管理类中小心copying行为

请记住:

1)复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。

2)普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。

条款15: 在资源管理类中提供对原始资源的访问

请记住:

1)APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理之资源”的办法。

2)对原始资源的访问尅经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。

条款16: 成对使用new和delete时要采用相同形式

16.1 尽量不要对数组形式做typedef动作。typedef int IntArra[10]; IntArra *p = new IntArra;应该使用delete[] p。

请记住:

如果你在new表达式中使用[],必须在相应的delete表达式中使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。

条款17: 以独立语句将newed对象置入智能指针

17.1 当将newed对象置入智能指针的同时,还有多条不能确定执行顺的语句,是很危险的。补充:在C++11下面,用make_ptr是最安全的用法。
  1.  
    //危险的用法
  2.  
    foo(shared_ptr<A>(new A), foo2());
  3.  
    //正确的用法
  4.  
    shared_ptr<int> p(new A);
  5.  
    foo(p, foo2());

请记住:

以独立语句将newed对象存储于(置入)只能指针内。如果不这样做,一旦异常抛出,有可能导致难以察觉的资源泄露。

4. 设计与声明

条款18: 让接口容易被正确使用,不易被吴用

请记住:

1)好的借口很容易被正确使用,不容易被吴用。你应该在你的所有的接口中女里达成这些性质。

2)“促进正确使用”的办法包括接口的一致性,以及与内置内的行为兼容。

3)“阻止无用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。

4)shared_ptr支持定制型删除器(custom deleter)。这可防范Dll问题,可被用来自动解除互斥锁等等。

条款19: 设计class犹如设计type

条款20: 宁以pass-by-reference-to-const替换pass-by-value

请记住:

1)尽量以pass-byreference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem)。

2)以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对他们而言,pass-by-value往往比较合适。

条款21: 必须返回对象时,别妄想返回其reference

请记住:

绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或者返回pointer或者reference指向一个local static对象而有可能同时需要多个这样的对象。

条款22: 将成员变量声明为private

22.1 原因:1)语法的一致性,2)对成员变量的处理有更精确的控制,3)封装。

请记住:

1)切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性。

2)protected并不比public更具封装性。

条款23: 宁以non-member、non-friend替换member函数

请记住:

宁可拿non-member、non-friend函数替换掉member函数,这样做可以增加封装性、包裹弹性(packaging flexibility)和技能扩展性。

条款24: 若所有参数皆需类型转换,请为此采用non-member函数

24.1 这样就成处理string str; "abc"+str; str + "abc"的(隐式转换)问题。

请记住:

如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。

条款25: 考虑写出一个不抛出异常的swap函数

25.1 在pimpl(pointer to implementation)类型的类中,如果采用深拷贝进行初始化、赋值,但在swap时,就该使用浅拷贝。这就需要自己定义swap函数。补充:在C++11中,这更应该定义move constructor和move assignment。

25.2 客户可以全特化std内的templates,但不可以添加新的templates(或者class或functions或其他任何东西)到std里头。

25.3 注意name lookup规则中的argument-dependent lookup。

请记住:

1)当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。

2)如果你提供一个member swap,也应该提供一个non-member swap用来调用前者。对于classes(而非templates),请特化std::swap。

3)调用swap时应针对std::swap使用using declaration,然后调用swap并且不带任何命名空间修饰符。

4)为”用户定义类型“进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。

5. 实现

条款26: 尽量延后变量定义式的出现时间

26.1 我们不仅应该延后变量的定义,知道非得使用的前一刻为止,甚至应该尝试延后这份定义直到能够给他初值实参为止。

26.2 这样可以避免构造(和析构)非要对象,还能够避免无意义的default构造行为。更深一层:以有意义的initializer来初始化,并附有说明变量的目的。

26.3 下面两种形式,做法A:1构造 + 1析构 + n赋值;做法B:n构造 + n析构。除非1)你知道赋值成本比“构造+析构”成本低,2)你正在处理代码中效率高度敏感(performance-sensitive)的部分,否则你应该使用做法B。

  1.  
    //做法A:定义于循环外
  2.  
    Widget w;
  3.  
    for(int i = 0; i < n; ++i){
  4.  
    w = 取决于i的某个值;
  5.  
    /*...*/
  6.  
    }
  7.  
    //做法B:定义于循环内
  8.  
    for(int i = 0; i < n; ++i){
  9.  
    Widget w(取决于i的某个值);
  10.  
    /*...*/
  11.  
    }

请记住:

以独立语句将newed对象存储于(置入)只能指针内。如果不这样做,一旦异常抛出,有可能导致难以察觉的资源泄露。

条款27: 尽量少做转型动作

27.1 在C++中,单一对象可能拥有一个以上的地址,尤其是在多继承时(单一继承也可能发生),所以,应避免做出“对象在C++如何布局”的假设。

请记住:

1)如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。

2)如果转型是必要的,试着将他隐藏与某个函数背后。客户随后可以调用该函数,而不需要将转型放到他们自己的代码内。

3)宁可使用C++-style(新式)转型,不要使用旧式转型。前者容易辨识出来,而且也比较有着分门别类的职掌。

条款28: 避免返回 handles指向对象内部成分

28.1 handle主要指:reference、pointer和iterator。

28.2 返回handle不仅有可能违反logical constness,而且就相当于公开内部成员,降低了封装性,另外还有dangling handle的可能。

请记住:

避免返回handles(包括reference、pointer、iterator)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性降至最低。

条款29: 为“异常安全”而努力是值得的

29.1 当抛出异常时,异常安全的函数会:1)不泄露任何资源,2)不允许数据败坏。

29.2 异常安全函数提供一下三种保证之一:1)基本保证:抛出后,状态是正常的,但处于哪个状态不可预料;2)强烈保证:前后状态保持一直;3)不抛掷保证。

29.3 声明不抛出异常,并不是说起不会抛出异常,而是说如果抛出异常将是严重错误,会有意想不到的函数被调用(unexpected)。

29.4 在强烈保证的函数中,常使用copy and swap技术(通常效率不高);如果一个函数调用多个(强烈保证的)函数,他就很难保证是强烈保证。

29.5 由于强烈保证在效率、复杂度上的成本太高,对许多函数而言,“基本保证“一个更好的选择。

请记住:

1)异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛出异常型。

2)“强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。

3)函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。

条款30: 透彻了解inlining的里里外外

30.1 类内定义的成员函数,隐含是inline。在类内定义的friend函数,也隐含为inline。

30.2 inline是请求,编译器可能忽略之。对于一个编译器愿意inlining的某个函数,编译器还是可能为之生成一个函数本体(当通过inline函数的地址调用时)。

30.3 构造函数和析构函数往往是inlining的糟糕候选人。

30.4 inline的缺点:1)代码量增大,2)程序库升级不便,3)调试不便

请记住:

1)将大多数inlining限制在小型、被频繁调用的函数身上。这可是日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会变大。

2) 不要只因为function templates出现在头文件,就将他们声明为inline。

条款31: 将文件间的编译依存关系降至最低

31.1 设计原则:1)如果使用object references或object pointers可以完成任务,就不要使用objects。2)如果能够,尽量以class声明式替换class定义式。3)为声明式和定义式提供两个不同的头文件。

31.2 对于参数或者返回类型是object的函数,声明时不需要object的定义式。

31.3 当声明式和定义式提供两个不同的头文件时,主要保持一致性。如头文件<iosfwd>。本条款也适用于templates。

31.4 通常使用pimlp(pointer to implementation)技术来实现。这样的类叫做Handle classes。

31.5 另外一个实现方式是通过interface classes。

31.5 Handle classes和interface classes实现,都会有一定程度的成本。

请记住:

1)支持“编译依存性最小化”的一般构想是:相依于声明,不要相依于定义式。基于此构想的两个手段是Hande Classes和Interface class。

2)程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。

6. 继承与面向对象设计

条款32: 确定你的public继承塑模出is-a关系

32.1 当Penguin继承Bird时,Bird中不宜有fly接口。Square继承rectangle不是好的设计。

请记住:

“public 继承”意味“is-a"。适用于base classes身上的每一件事情一定也适用于derived class身上,因为每一个derived class对象也都是一个base class对象。

条款33: 避免遮掩继承而来的名称

33.1 转交函数,就是在private继承的derived类中,将virtual函数的override定义写在public标签下。

请记住:

1)derived classes内的名称会遮盖base classes内的名称。在public继承下从来没有人希望如此。

2)为了让被遮盖的名称重见天日,可使用using declaration或转交函数(forwarding functions)。

条款34: 区分接口继承和实现继承

34.1 pure virtual函数、impure virtual函数、non-virtual函数分别为:只继承接口、继承接口和一份缺省实现、继承接口和一份强制实现。

34.2 当pure virtual函数也有实现时,继承接口+可以使用一份(默认)实现。

请记住:

1)接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。

2)pure virtual函数只具体指定接口继承。

3)普通impure virtual函数具体指定接口继承及缺省实现继承。

4)non-virtual函数具体指定接口继承以及强制性实现继承。

条款35: 考虑virtual函数以外的其他选择

请记住:

1)virtual函数的替代方案包括NVI(non-virtual interface)手法以及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。

2)将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。

3)tr1::function(在C++11中已经成为std::function)对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用物(callable entities)

条款36: 绝不重新定义继承而来的non-virtual函数

请记住:

绝不重新定义继承而来的non-virtual函数。

条款37: 绝不重新定义继承而来的缺省参数

37.1 virtual函数系动态绑定,而缺省参数值确实静态绑定。

37.2 当virtual函数有默认值时,父类和派生类的默认值要一样,这无疑加重的程序员的负担,这是可以考虑virtual的替代设计。

请记住:

绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。

条款38: 通过复合塑模出has-a或”根据某事物实现出“

38.1 public继承带有is-a的意义,而复合(composition,组合)意味着has-a或is-implemented-in-terms-of。

请记住:

1)复合(composition)的意义和public继承完全不同。

2)在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)。

条款39: 明智而审慎地使用private继承

39.1 private继承不能做derived-to-base的转换。

39.2 private继承在软件“设计”层面上没有意义,其意义只及于软件实现层面。private继承意味着只有实现部分继承,接口部分应略去。

39.3 对于空白类,他的大小不为0,复合到其他类中时,也要占用一定空间;但是当作为其他类的基类时,并不占用空间。这就是所谓的EBO(empty base optimization)

请记住:

1)private继承意味着is-implemented-in-terms-of(根据某物实现出)。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。

2)和复合(composition)不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。

条款40: 明智而审慎地使用多继承

请记住:

1)多重继承比单一继承复杂。他可能导致行的歧义性,以及对virtual继承的需要。

2)virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具有使用价值的情况。

3)多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相组合。

7. 模板编程与泛型编程

条款41: 了解隐式接口和编译器多态

请记住:

1)classes和templates都支持接口(interfaces)和多态(polymorphism)

2)对classes而言接口是显示的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期。

3)对template参数而言,接口是隐式(implicit),奠基于有效表达式。多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期。

条款42: 了解typename的双重意义

42.1 在template编程中,typename必须作为嵌套从属类型的前缀名,但是也有两点例外:在base class list或member initialization list中。

  1.  
    template<typename T>
  2.  
    class Derived : public Base<T>::Nested{
  3.  
    public:
  4.  
    Derived(int x):Base<T>::Nested(x){
  5.  
    typename Base<T>::Nested temp;
  6.  
    }
  7.  
    };

42.2 由于每次用到嵌套从属类型都需要加typename,通常做法是定义别名。

  1.  
    template<typename T>
  2.  
    void f(T t){
  3.  
    typedef typename std::iterator_traits<T>::value_type value_type;
  4.  
    value_type val(*t);
  5.  
    ...
  6.  
    }

请记住:

1)声明template参数时,前缀关键字class和typename可互换。

2)请使用关键字typename标示嵌套从属类型名称;但不得在base class list(基类列表)或member initialization list(成员初始化列表)内以它作为base class 修饰符。

条款43: 学习处理模板化基类的名称

43.1 当基类为模板类时,并不能直接使用来自基类的名称。基类模板的通用版本,与基类模板针对某个类型的特化版本,可能在内部name、接口上完全不一样;所以编译器无法判断要使用的name是否来自基类模板。

43.2 三个办法使用基类的名字:1)名字前加sthis->;this->name()。2)使用前先using declaration一下name;using Base<T>::name;但是有对name修改访问权限的副作用。3)名字前直接加基类域限定符;Base<T>::name()。当name是virtual函数名时,这样就会关闭virtual绑定行为,这是很糟糕的。当然,这三个方法的一个公共前提是:派生类对基类的这个名字有访问权限。

请记住:

可在derived class templates内通过“this->"指涉base class templates内的成员名称,或藉由一个明白写出的”base class 资格修饰符“完成。

条款44: 将与参数无关的代码抽离templates

请记住:

1)Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。

2)因非类型模板参数(non-type template parameter)而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。

3)因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具体类型(instantiation types)共享代码。

条款45: 运用成员函数模板接受所有兼容类型

45.1 在class内声明泛化copy构造函数(member template)并不会阻止编译器生成他们自己的copy构造函数。如果不希望使用合成的copy构造函数,就需要定义正常的copy构造函数。相同规则同样适用于赋值(assignment)操作。注意:定义了泛化的构造函数,编译器就不会再合成默认构造函数了。

请记住:

1)请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数。

2)如果你声明member template用于“泛化copy构造”或“泛化assignment操作”,你还需要声明正常的copy构造函数和copy assignment操作符。

条款46: 需要类型转换时请为模板定义非成员函数

46.1 当用模板实现“条款24”的功能时,往往存在很多错误。下面的方法1中,模板参数无法匹配,编译无法通过;方法2中,根据oneHalf实例化了Rational<int>,friend函数operator*就别声明出来了,现在operator*就不再是函数模板,是函数了,支持实参的隐式转换了,所以编译可通过,但找不到定义(因为没有定义operator*(const Rational<int>&, Rational<int>&),所以链接不通过。方法3中,分析同上,区别在于,实例化类的时候operator*也被定义了。将friend函数定义在类内,就隐含为inline,但如果想避开inline的弊端(条款30),可以按照做法4实现。

  1.  
    //============做法1============
  2.  
    template<typename T>
  3.  
    class Rational{
  4.  
    public:
  5.  
    Rational(const T& n, const T& d);
  6.  
    /* ... */
  7.  
    };
  8.  
    template<typename T>
  9.  
    const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){
  10.  
    /* ... */
  11.  
    }
  12.  
    Rational<int> result = oneHalf * 2;//错误!无法编译通过。
  13.  
    //============做法2============
  14.  
    template<typename T>
  15.  
    class Rational{
  16.  
    public:
  17.  
    Rational(const T& n, const T& d);
  18.  
    friend const Rational operator*(const Rational& lhs, const Rational& rhs);
  19.  
    /* ... */
  20.  
    };
  21.  
    template<typename T>
  22.  
    const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){
  23.  
    /* ... */
  24.  
    }
  25.  
    Rational<int> result = oneHalf * 2;//编译通过,链接错误!
  26.  
    //============做法3============
  27.  
    template<typename T>
  28.  
    class Rational{
  29.  
    public:
  30.  
    Rational(const T& n, const T& d);
  31.  
    friend const Rational operator*(const Rational& lhs, const Rational& rhs){
  32.  
    /* ... */
  33.  
    }
  34.  
    /* ... */
  35.  
    };
  36.  
    Rational<int> result = oneHalf * 2;//编译通过,链接通过!
  37.  
    //============做法4(非inline做法)============
  38.  
    template<typename T>
  39.  
    const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs){
  40.  
    /* ... */
  41.  
    }
  42.  
    template<typename T>
  43.  
    class Rational{
  44.  
    public:
  45.  
    Rational(const T& n, const T& d);
  46.  
    friend const Rational operator*(const Rational& lhs, const Rational& rhs){
  47.  
    doMultiply(lhs, rhs);
  48.  
    }
  49.  
    /* ... */
  50.  
    };
  51.  
    Rational<int> result = oneHalf * 2;//编译通过,链接通过!

请记住:

当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。

条款47: 请使用traits classes表现类型信息

请记住:

1)Traits classes使得“类型相关信息”在编译期可用。他们以templates和“templates特化”完成实现。

2)整合重载技术(overloading)后,traits classes有可能在编译期对类型执行if...else测试。

条款48: 认识template元编程

48.1 上一条款中基于iterator_traits来做重载(条件选择)也是template metaprogramming(TMP)的体现。

48.2 某些东西在TMP比在“正常的”C++容易。若将上一条款中的重载方式,替换成如下代码,对于不支持 iter += d的类型就会编译错误。因为编译期必须确保所有源代码有效,纵使是不会执行起来的代码!所以模板编程不宜与RTTI一起使用。

  1.  
    template<typename IterT, typename DistT>
  2.  
    void advance(IterT& iter, DistT d){
  3.  
    if(typid(typename std::iterator_traits<IterT>::iterator_category)
  4.  
    ==typeid(std::random_access_iterator_tag))
  5.  
    iter += d;
  6.  
    else
  7.  
    /* ... */
  8.  
    }

请记住:

1)Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和跟高的执行效率。

2)TMP可被用来生成“基于策略选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

8. 定制new和delete

条款49: 了解new-handler的行为

49.1 new_handler在std中定义:

  1.  
    namespace std{
  2.  
    typedef void (*new_handler)();
  3.  
    new_handler set_new_handler(new_handler p) throw();
  4.  
    }

49.1 一个设计良好的new-handler函数,应该做一下事情:1)让更多内存可被使用;2)安装另一个new-handler;3)卸除new-handler;4)抛出bad_alloc,这样的异常不会被operator new捕捉,因此会被传播到内存索求处;5)不返回,通常调用abort或exit。

请记住:

1)set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。

2)Nothrow new是一个颇为局限的工具,因为它只适用于内存分配 ;后续的构造函数调用还是可能抛出异常。

条款50: 了解new和delete的合理替换时机

请记住:

有许多理由需要写一个自定的new和delete,包括改善效能,对heap运用错误进行调试、收集head的使用信息。

条款51: 编写new和delete时需固守常规

请记住:

1)operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0bytes申请。class专属版本则还应该处理“比正确大小更大的(错误)申请”。

2)operator delete应该在收到null指针时不做任何事。class专属版本则还应该处理“比正确大小更大的(错误)申请。

条款52: 写了placement new也要写placement delete

请记住:

1)当你写一个placement operator new,请确定也写出了对应的placement operator delete。如果没有这么做,你的程序可能发生隐微而时断时续的内存泄露。

2)当你声明placement new和placement delete,请确定不要无意识(非故意)地掩盖了他们的正常版本。

9. 杂项讨论

条款53: 不要轻忽编译器的警告

请记住:

1)严肃对待编译器器发出的警告信息。努力在你的编译器的最高(最苛刻)警告级别下争取”无任何警告“的荣誉。

2)不要过度依赖编译器的报警能力,因为不同的编译器对待事物的态度并不相同。一旦移植到另一个编译器上,你原本依赖的警告信息有可能消失。

条款54: 让自己熟悉包括TR1在内的标准程序库

请记住:

1)C++标准程序库的主要机能有STL、iostreams、locales组成。并包含C99标准库。

2)略。。。(注:应该参考C++11的新特性

3)TR1自身只是一份规范。为获得TR1提供的好处,你需要一份礼物。一个好的事物来源是Boost。

条款55: 让自己熟悉Boost

请记住:

1)Boost是一个社群,也是一个网站。致力于免费、源码开放、同僚复审的C++程序库开发。Boost在C++标准化的过程中扮演深具影响力的角色。

2)Boost提供许多TR1组件实现品,以及其他许多程序库。

《Effective C++(第三版)》-笔记的更多相关文章

  1. HTML+CSS笔记 CSS笔记集合

    HTML+CSS笔记 表格,超链接,图片,表单 涉及内容:表格,超链接,图片,表单 HTML+CSS笔记 CSS入门 涉及内容:简介,优势,语法说明,代码注释,CSS样式位置,不同样式优先级,选择器, ...

  2. CSS笔记--选择器

    CSS笔记--选择器 mate的使用 <meta charset="UTF-8"> <title>Document</title> <me ...

  3. HTML+CSS笔记 CSS中级 一些小技巧

    水平居中 行内元素的水平居中 </a></li> <li><a href="#">2</a></li> &l ...

  4. HTML+CSS笔记 CSS中级 颜色&长度值

    颜色值 在网页中的颜色设置是非常重要,有字体颜色(color).背景颜色(background-color).边框颜色(border)等,设置颜色的方法也有很多种: 1.英文命令颜色 语法: p{co ...

  5. HTML+CSS笔记 CSS中级 缩写入门

    盒子模型代码简写 回忆盒模型时外边距(margin).内边距(padding)和边框(border)设置上下左右四个方向的边距是按照顺时针方向设置的:上右下左. 语法: margin:10px 15p ...

  6. HTML+CSS笔记 CSS进阶再续

    CSS的布局模型 清楚了CSS 盒模型的基本概念. 盒模型类型, 我们就可以深入探讨网页布局的基本模型了.布局模型与盒模型一样都是 CSS 最基本. 最核心的概念. 但布局模型是建立在盒模型基础之上, ...

  7. HTML+CSS笔记 CSS进阶续集

    元素分类 在CSS中,html中的标签元素大体被分为三种不同的类型:块状元素.内联元素(又叫行内元素)和内联块状元素. 常用的块状元素有: <div>.<p>.<h1&g ...

  8. HTML+CSS笔记 CSS进阶

    文字排版 字体 我们可以使用css样式为网页中的文字设置字体.字号.颜色等样式属性. 语法: body{font-family:"宋体";} 这里注意不要设置不常用的字体,因为如果 ...

  9. HTML+CSS笔记 CSS入门续集

    继承 CSS的某些样式是具有继承性的,那么什么是继承呢?继承是一种规则,它允许样式不仅应用于某个特定html标签元素,而且应用于其后代(标签). 语法: p{color:red;} <p> ...

  10. HTML+CSS笔记 CSS入门

    简介: </span>年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的<span>脚本解释程序</span>,作为ABC语言的一种继承. & ...

随机推荐

  1. vue.js 源代码学习笔记 ----- 工具方法 perf

    import { inBrowser } from './env' export let mark export let measure if (process.env.NODE_ENV !== 'p ...

  2. Java中关于内存泄漏出现的原因以及如何避免内存泄漏

    转账自:http://blog.csdn.net/wtt945482445/article/details/52483944 Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是静 ...

  3. Android深入理解JNI(一)JNI原理与静态、动态注册

    前言 JNI不仅仅在NDK开发中应用,它更是Android系统中Java与Native交互的桥梁,不理解JNI的话,你就只能停留在Java Framework层.这一个系列我们来一起深入学习JNI. ...

  4. Git详解之四 服务器上的Git

    以下内容转载自:http://www.open-open.com/lib/view/open1328069988843.html 服务器上的 Git 到目前为止,你应该已经学会了使用 Git 来完成日 ...

  5. .NET实现CORS跨域

    1.NuGet:Install-Package Microsoft.AspNet.WebApi.Cors 2.Application_Start中增加(包含在#if DEBUG中):GlobalCon ...

  6. MYSQL 调优和使用必读

    转载自:http://blog.eood.cn/mysql#rd?sukey=fc78a68049a14bb29c60f21c5254b15a1a9234459cf25ff467de14129ca11 ...

  7. 让svn具有分布式的功能。

    最近开发遇到了个难事.公司的svn库不能随便提交,必须要经过验证.但是平时修改太多,如果不提交到svn说不定前面被删掉的代码后面又需要了.svn自带的relocate和switch都不能达到要求.找遍 ...

  8. 优化 UWP 中图片的内存占用

    跟图片打交道的 UWP 应用或多或少都会遇到图片带来的性能问题,就算不主要处理图片,做个论坛做个新闻客户端都涉及到大量图片.一个帖子.一篇文章里多半都是些高清大图,这些图片一张即可占用程序 1~2M ...

  9. python笔记-13 mysql与sqlalchemy

    一.RDBMS relational database management system 关系型数据库引入 1.数据库的意义 更有效和合理的存储读取数据的一种方式 关系模型基础上的数据库 -> ...

  10. python3 scrapy main运行调式

    from scrapy.cmdline import execute import sys import os sys.path.append(os.path.dirname(os.path.absp ...