• Effective 笔记


                                                             

     

     

     

     

     

     

    1,C++属于一个语言联邦 : C     Object-Oriented C++     Template C++       STL

    2,应尽量以const,enum,inline替换#define

      如果一个常量是class专属常量又是static,且为整数类型(int,char,bool),则需特殊处理:只要不取它们的地址,可以声明并使用它们而不须提供定义式。

    但是如果取某个class专属常量的地址,或纵使不取其地址而编译器却(不正确的)坚持要看到一个定义式,必须提供定义式

    enum的行为某方面比较像#define而不像const:取一个const地址合法,但是取一个enum地址就不合法,而取#define也不合法,如果不想让被人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束

    #define和enum不会设定额外的空间,const在优秀的编译器中也许可能也是这样,但是不够优秀的编译器就必须设定额外的空间

    3,尽可能使用const

    const语法虽然变化多端,但并不莫测高深。如果关键字const出现在*左侧,表示被指物是常量;如果出现在*右边,表示指针自身是常量;如果出现在*两边,表示被指物和指针都是常量

    std::vector<int>  vec;

    const std::vector<int>::interator  iter = vec.begin()                    T*const

    std::vector<int>:const_iterator iter = vec.begin()                       const T*

     

    将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上

    const特性的两个函数可以重载(必须在类中,不在类中会出现问题) const函数不可改变对象任何变量(注意区分指针的情况,只要指针不变,指向的值可变化)

    注意操作符[]的重载要返回值的引用,否则不能对结果赋值(相当于对值的一个拷贝赋值,没有意义)

    mutable可以在常函数中修改其值,但是一般作用是:1,用于缓存2,或必须在常函数中修改值

     

    利用const operator[]实现出non-const版本:

    class TextBlock {

    public:

               const char & operator[](std::size_t position) const {  .....               return text[position]; }

               char & operator[](std::size_t position){

                      return  const_cast<char &>(  static_cast<const  TextBlock&> (*this)[position]);

    };

     

    非const调用const是安全的

    const调用非const是不安全的,(因为非const有可能改变)

    总结:

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

          2>编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)

          3>当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复

    4,确定对象被使用之前已被初始化:

    为了保证一定性,使用内建数据类型一定要初始化,构造对象要保证构造函数初始化它的成员

    C++ 对“定义在不同的编译单元内的non-local  static对象”的初始化相对次序并无明确定义。因为决定它们初始化次序相当困难,根本无解

    最常用的形式,就是  多个编译单元内的non-local static对象经由“模板隐式具体化,implicit template instantiations"形式(而后者自己可能也是经由”模板隐式具现化“形成)

     

    任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。处理方法:在程序的单线程启动阶段手工调用所有的reference-returning函数,这可消除与初始化有关的“竞速形势”

     

    为了避免在对象初始化之前过早地使用它们,你需要做三件事:

    第一,手工初始化内置型non-member对象。

    第二,使用成员初值列对付对象的所有成分

    第三,在”初始化次序不缺定性“(这对不同编译单元所定义的non-local static对象是一种折磨)氛围下加强设计

     

    总结:

                1>为内置对象进行手工初始化,因为C++不保证初始化它们

                2>构造函数最好使用成员初始化列表,而不要在构造函数体内使用赋值操作。次序和变量声明的顺序一致

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

    5,编译器默认提供四个public且inline函数,赋值,拷贝,构造,析构,但是它们是在需要的时候才被编译器创建出来的

    C++不允许让引用改指向不同的对象

    >如果打算在一个“内含reference成员”的class内支持赋值操作,必须自己定义copy assignment操作符

    >如果某个base class将copy assignment操作符声明为private,编译器将拒绝为其derived classes 生成copy assignment操作符

     

    6,若不想使用编译器自动生成的函数,就该明确拒绝:将他们声明为private,同时不要实现他们,这样的话即使friend或者成员函数调用他们连接器会提示错误

    另外一种方法是把他们声明为private在一个单独的类中,然后用这个类继承它

    7,为多态基类声明virtual析构函数

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

    >classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数

    8,别让异常逃离析构函数

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

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

    9,绝不在构造和析构过程中调用virtual函数

    >无法使用virtual函数从base classes向下调用,在构造期间,可以藉由“令derived classes将必要的构造信息向上传递到base class构造函数”替换加以弥补

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

    10,令operator=返回一个 reference to *this

                                                      目的是:为了实现“连锁赋值”

    11,在operator=中处理“自我赋值”

    自我赋值     发生在对象被赋值给自己时

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

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

    12,复制对象时勿忘其每一个成分

    自己编写copying函数,要确保:

    1,复制所有local成员变量

    2,调用所有base classes内的适当copying函数

     

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

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

     

    所谓资源就是:一旦用了它,将来必须还给系统,否则会出现问题。C++最常用的资源就是动态内存分配,当然还有其他资源:文件描述扶,互斥锁,图形界面中的字型和笔刷、数据库连接、以及网络sockets

    13,以对象管理资源

    两个关键想法:

    >获得资源后立刻放进管理对象内

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

     

    由于auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr同时指向同一对象。如果真是那样,对象会被删除一次以上,而那会使你的程序搭上驶向“未定义行为”的快速列车上。为了预防这个问题,auto_ptr有一个不寻常的性质:若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将取的资源的唯一控制权!!!(该性质有利也有弊)

     

    auto_ptr的替代方案是“引用计数型智慧指针”(RCSP),RCSP也是个智能指针,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。RCSP提供的行为类似垃圾回收,不同的是RCSP无法打破环状引用:::tr1:shared_ptr就是一个RCSP

     

    注意:auto_ptr和tr1::shared_ptr两者都在析构函数内做delete而不是delete[]动作!!如果对数组指针调用则是馊主意

     

    >为了防止资源泄漏,请使用RAII对象,它们在构造函数中活得资源并在析构函数中释放资源 (Resource Acquisition Is Initialization,RAII)

    >两个常被使用的RAII  classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,已经其copy行为比较直观。若选择auto_ptr,复制动作会时它(被复制物)指向null

    14,在资源管理类中小心copying行为

    在RAII中当一个对象被复制时,出现严重后果,一般有两种方案:

    >禁止复制(声明为private)

    >对底层资源祭出“引用计数法”(reference-count)           这种做法一般如果内含有一个tr1::shared_ptr ,但是默认事件是count为0时删除,幸运的是我们可以自己指定为0时的事件

     

    类的析构函数会自动调用其non-static成员变量的析构函数

    复制底部资源                             (深度拷贝)

    转移底部资源的拥有权              (auto_ptr)

     

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

    tr1::shared_ptr和auto_ptr都提供了一个get函数,用来执行显式转换,也就是它返回智能指针内部的原始指针,以便于直接访问时可以通过

    它们还重载了(->和*)操作符

     

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

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

     

    16,成对使用new和delete时要采用相同形式

               最好不要用typedef,否则会造成语义不清:

             typedef  std::string AddressLines[4];

             std::string  *pal = new AddressLines;

             delete pal;          错误!!!

             delete[] pal ;       正确!!!

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

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

     

    18,让接口容易被正确使用,不易被误用

    >好的接口很容易被正确的使用,不容易被误用。应该在所有的接口中努力达成这些性质

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

    >“组织误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任

    >tr1::shared_ptr支持定制型删除器。这个防范DLL问题,可被用来自动解除互斥锁等等

     

    19,设计class犹如设计type

    好的types是一项艰巨的工作。好的types有自然的语法,直观的语义,以及一或多个高效的实现品

    如何设计好的classes:

    >新的type的对象应该如何被创建和销毁

    >对象的初始化和对象的赋值该有什么差别

    >新的type的对象如果被passed by value(以值传递),意味着什么

    >什么是新type的“合法值”

    >新的type需要配合某个继承图系么

    >新的type需要什么样的转换

    >什么样的操作符和函数对此新type而言是合理的

    >什么样的标准函数应该驳回

    >谁该取用新type的成员

    >什么是新type的“未声明接口”

    >新type有多么一般化

    >真的需要一个新type么

     

    20,宁以pass-by-reference-to-const替换pass-by-value

    >尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题

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

     

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

    任何时候看到一个reference声明式,都应该立刻问自己,它的另一个名称是什么?因为它一定是某物的另一个名称

    任何函数如果返回一个reference指向某个local对象,都将一败糊涂

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

     

    22,将成员变量声明为private

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

    >protected并不比public更具封装性

     

    23,宁以non-menber、non-friend替换menber函数

    这样做可以增加封装性、包裹弹性和机能扩充性

     

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

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

     

    25,考虑一个不抛出异常的swap函数

     pimpl手法    :point to implementation

    C++只允许对class template偏特化,在function templates身上偏特化行不通,解决方案是:为它添加一个重载版本

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

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

    >调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”

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

     

    26,尽可能延后变量定义式的出现时间

     >尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序的效率。

     

    27,尽量少做转型动作

    >const_cast通常被用来将对象的常量性转除。它也是唯一有此能力的C++-style转型操作符

    >dynamic_cast主要用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系中的某个对象。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作

    >reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就是表示它不可移植。例如将point to int转型为int

    >static_cast用来强迫饮食转换。例如将non-const转型const对象,或将int转型double等等。但是无法将const转型non-const

     

    任何一个类型转换(不论是通过转型操作而进行的显式转换,或通过编译器完成的隐式转换)往往真的令编译器编译出运行期执行的码

     

    单一对象(例如Derived对象)可能拥有一个以上的地址(例如“以base *指向它”时的地址和“以Derived *指向它”时的地址)行为

    之所以需要dynamic_cast,通常是因为想在一个认定为derived对象身上执行derived class操作函数,但手上却只有一个"指向base”的pointer或reference,只能靠它们呢来处理对象

    绝对必须避免的一件事是所谓的"连串 dynamic_casts"

     

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

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

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

     

    28,避免返回handles指向对象内部成分

     两个教训:

              --->成员变量的封装性最多只等于"返回其reference"的函数的访问级别

              --->如果const成员函数传出一个reference,后者所指数据与对象自身关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据

    handles: 指针,reference,迭代器

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

     

    29,为"异常安全"而努力是值得的

    当异常被抛出时,带有异常安全性的函数会:

    =>不泄露任何资源

    =>不允许数据败坏

     

    异常安全函数提供以下三个保证之一:

    ==>基本承诺

    ==>强烈保证

    ==>不抛掷保证

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

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

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

    30,透彻了解inlining的里里外外

    inline只是对编译器的一个申请,不是强制命令

    inline函数通常 一定 被置于头文件内

    inline 在大多数C++程序中时编译器行为,只有少数基于.NET CLI(Common Language Infrastructure,公共语言基础设施)可以在运行期完成inlining

    Template通常也被置于头文件中,以为一旦被使用,编译器为了将它具现化,需要知道它长什么样子(像inline),(并不是同一准则,某些建置环境可以在连接期执行template具现化)

    编译器拒绝将过于复杂的函数(循环或递归)的函数inlining。而所有对virtual函数的调用(除非最平淡无奇的)也对会使inling落空,因为virtual是运行期确定

    编译器通常不对“通过函数指针而进行的调用”实施inlining,因为要确定地址

    inline函数无法升级

    很多调试器对inline函数束手无策

     

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

    >不要只因为function templates出现在头文件,就将它们声明为inline

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

    如果使用object references 或 object pointers可以完成任务,就不要使用objects

    如果能够,尽量以class声明替换class定义式

    为声明式和定义式提供不同的头文件

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

    >程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用

    继承与面向对象

    32,确定public继承塑模出is-a关系

    以C++进行面向对象编程,最要的一个原则:公开继承 意味 "is-a“的关系

    企鹅是一种鸟,但企鹅不会飞 理论解决办法:1,双继承  2,重新(一个错误的)飞功能  3,都不写飞功能

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

    base class对象

    33,避免遮掩继承而来的名称

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class Base {
     5 private:
     6     int x;
     7 public:
     8     virtual void mf1() = 0;
     9     virtual void mf1(int);
    10     virtual void mf2();
    11     void mf3();
    12     void mf3(double);
    13 };
    14 
    15 class Derived : public Base {
    16 public:
    17     virtual void mf1();
    18     void mf3();
    19     void mf4();
    20 };
    21 
    22 int main()
    23 {
    24     Derived d;
    25     int x;
    26 
    27     d.mf1();   //Derived::mf1
    28     d.mf1(x);  //ERROR !!!  NO ARGS              using Base::mf1
    29     d.mf2();   //Base::mf2
    30     d.mf3();   //Derived::mf3
    31     d.mf3(x);  //ERROR !!! NO ARGS               using Base::mf3
    32 
    33     return 0;
    34 }

     上面的程序只是为了说明编译时候的正确性,因此有些函数没有实现,链接肯定通不过,不过能说明问题

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

    >为了让被遮掩的名称再见天日,可使用using声明式或转交函数

    34,区分接口继承和实现继承

     声明一个pure virtual函数的目的是为了让derived classes只继承函数接口

     声明简朴的(非纯)impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现

     声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现

     一个典型的程序有80%的执行时间花费在%20的代码身上

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

    >pure virtual函数只具体指定接口继承

    >简朴的(非纯)impure virtual函数具体指定接口继承及缺省实现继承

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

    35,考虑virutal 函数以外的其他选择

    >virtual函数的替代方案包括NVI手法及Stratrgy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式

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

    >tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定值目标签名兼容”的所有可调用物

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

    >绝对不要重新定义继承而来的non-virtual

    37,绝不重新定义继承而来的缺省参数值

    virtual函数系动态绑定,而缺省参数值却是静态绑定

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

     

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

    >复合的意义和public继承完全不同

    >在应用域,复合意味has-a。在实现域,复合意味is-implemented-in-terms-of(根据某物实现出)

    39,明智而审慎的使用private继承

    private继承主要用于“当一个意欲成为derived class者想访问一个意欲成为base class者的protected成分,或为了重新定义一或多个virtual函数

    EBO  empty base optimization 空白基类最优化,只适用于单一继承

    复合和private继承都意味 is-implemented-in-terms-of

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

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

    40,明智而审慎的使用多重继承

    C++编译器解析重载函数调用的规则:在看到是否有个函数可取用之前,C++首先确认这个函数对此调用的最佳匹配。找出最佳匹配函数后才检验其可取用性

    virtual base的初始化责任是由继承体系中的最低层class 负责。这暗示:

    1>classes若派生自virtual bases而需要初始化,必须认知其virtual bases——不论那些bases距离多远

    2>当一个新的derived class加入继承体系中,它必须承担起virtual bases(不论直接还是间接)的初始化责任

    对待virtual base classes: 1,非必须不是有2,如果必须使用,尽可能避免在其中放置数据

    >多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要

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

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

    41,了解隐式接口和编译期多态

    Templates及泛型编程的世界,与面向对象有根本的不同。在此世界中显式接口和编译期多态依然存在,但重要性降低。反倒是隐式接口和编译期多态移到前面了

    >classes和templates都支持接口和多态

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

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

    42,了解typename的双重意义

    template内出现的名称如果相依于某个template参数,称之为从属名称

    如果C++解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,除非告诉它是(typename)

    typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可以在member initialization list中作为base class修饰符

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

    >请使用关键字typename标识嵌套从属类型名称;但不得在base class list(基类列)或member initialization list内以它作为base class修饰符

    43, 学习处理模板化基类内的名称

    class 定义式最前面的"template<>"语法象征这既不是template也不是标准的class 。而是模板全特化

    C++编译期往往拒绝在templatized base classes(模板化基类)内寻找继承而来的名称,因为有可能特化而拥有不同的接口!可这是个严重的问题,我们可以有三个办法解决:

    1,在调用base class函数动作之前加上this->

    2, 使用using声明式

    3, 明白指出被调用函数位于base class内,但是如果是虚函数的话会导致关闭虚函数"virtual 绑定行为"

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

    44, 将于参数无关的代码抽离templates

    >Template生成多个classses和多个函数,所以任何template代码都不该与某个构造成膨胀的template参数产生相依关系

    >因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数

    >因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码

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

    >请使用member function templates生成"可接受所有兼容类型"的函数

    >如果你声明member templates用于"泛化copy构造"或"饭或assignment操作",你还是需要声明正常的copy构造函数和copy assignment操作符

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

    在template实参推导过程中从不将隐式类型转换函数纳入考虑

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

    47, 请使用traits classes表现类型信息

    >Traits classes使得"类型相关信息"在编译期可用。它们以templates和"templates特化"完成实现

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

    48, 认识template元编程

     1 #include <iostream>
     2 
     3 template<unsigned n>
     4 struct Factorial
     5 {
     6     enum { value = n * Factorial<n-1>::value};
     7 };
     8 
     9 template<>
    10 struct Factorial<0> {
    11     enum { value = 1 };
    12 };
    13 
    14 
    15 int main(int argc,char *argv[])
    16 {
    17     std::cout << Factorial<5>::value << std::endl;
    18     std::cout << Factorial<10>::value << std::endl;
    19 
    20     return 0;
    21 }


    好处:1,确保量度单位正确 2,优化矩阵 3, 可以生成客户定制之设计模式

    >Template metaprograming可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率

    >TMP可被用来生成"基于政策选择组合"的客户定制代码,也可用来避免生成对某些特殊类型不适合的代码

    49,了解new-handler的行为

    new-handler函数必须做以下事情:

    1= 让更多内存可被使用

    2= 安装另一个new-handler

    3= 卸除new-handler

    4= 抛出bad_alloc(或派生自bad_alloc)的异常

    5= 不返回

    operator new要做以下事情:

    1= 调用set_new_handler,告知Widget的错误处理函数。这会将class的new-handler安装为global new-handler

    2= 调用global operator new,执行实际之内存分配。如果分配失败,global operator new会调用class的new-handler,因为那个函数才刚被安装为global new-handler。如果global operator new最终无法分配足够内存,会抛出一个bad_alloc异常。在此情况下class的operator new必须恢复原来的global new-handler,然后再传播该异常。为确保原来的new-handler总是能够被重新安装回去,class将global new-handler视为资源并遵守忠告,运用资源管理对象防止资源泄漏

    3= 如果global operator new能够分配足够一个class对象所用的内存,class的operator new会返回一个指针,指向分配所得。class析构函数会管理golbal new-handler,它会自动将class ‘s operator new被调用前的那个global new-handler恢复回来

    50,了解new和delete的合理替换机制

    51,编写new和delete时需固守常规

    52,写placement new 也要写 placement delete

    53,不要轻易忽略编译期警告

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

    55,让自己熟悉Boost

  • 相关阅读:
    【转载】RocketMQ与Kafka对比(18项差异)
    canal —— 阿里巴巴mysql数据库binlog的增量订阅&消费组件
    CodingTMD’s Reading List
    [LeetCode] Evaluate Reverse Polish Notation, Solution
    [LeetCode] Clone Graph, Solution
    [LeetCode] Sort List, Solution
    [LeetCode] Max Points on a Line, Solution
    [LeetCode] LRU Cache, Solution
    [LeetCode] Binary Tree Preorder Traversal, Solution
    [LeetCode] Reorder List, Solution
  • 原文地址:https://www.cnblogs.com/lfsblack/p/2745460.html
Copyright © 2020-2023  润新知