• 《Effective C++(第三版)》-笔记


    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组件实现品,以及其他许多程序库。

  • 相关阅读:
    Object的公用方法
    Java的特点
    Set集合
    Java语言的三大特性
    List集合
    Collection类
    HashSet
    Codeforces1141F2 Same Sum Blocks (Hard)
    Codeforce1176F Destroy it!
    jzoj 5348. 【NOIP2017提高A组模拟9.5】心灵治愈
  • 原文地址:https://www.cnblogs.com/lenmom/p/9252211.html
Copyright © 2020-2023  润新知