《Effective C++》读书摘要

来源:互联网 发布:淘宝卖家真实姓名隐藏 编辑:程序博客网 时间:2024/04/28 03:55

来源: <http://www.cnblogs.com/fanzhidongyzby/archive/2012/11/18/2775603.html> 

最近刚读完侯捷的《Effective C++》,相对来说,这本书的内容比较贴近基础,对于刚掌握C++基础的人会有不少的提高。不过书中还是涉及了不少C++的高级特性,阅读起来需要查阅相关的资料。书中给出了大量的示例和代码来说明具体规则的原理,我按照书中给出的标题将每个条目的关键内容整理如下。一方面是保留一份读书笔记,另一方面也是为了方便日后查阅方便。当然,如果不能从简单摘要的内容回忆起具体信息,到时再查书也不迟。同时也期望大家能从中找到自己没有注意的知识点,有所提高,大牛勿喷

(一)、让自己习惯C++

一、C++语言联邦

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

(View C++ as a federation of languages)

多重范型编程语言:过程式(procedural)、面向对象式(object-oriented)、函数式编程(functional)、泛型编程(generic)、模板元编程(metaprogramming)。

为了理解C++,你必须认识其主要的次语言,总共只有四个:

  •     ■ C.
  •     ■ Object-Oriented C++.
  •     ■ Template C++.
  •     ■ STL. 
    
请记住:
    ■ C++高效编程守则视状况而变化,取决于你使用C++的哪一部分.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4235941>


二、constenuminline替换#define

条款02:尽量以const,enum,inline替换#define
(prefer const,enum,inline to #define)

比如你写下如下常量定义:#define MAX_BUFFER_LEN (50),假如编译代码的时候由于MAX_BUFFER_LEN导致的编译错误时,编译的信息里面也许会提到50这个数字而不是MAX_BUFFER_LEN,因为MAX_BUFFER_LEN有可能就没有被写入符号表(symbol table),这就给你带来了困惑了,如果你的项目比较大,源码文件较多,你就会对50这个数字毫无概念,不知道它来自哪里,那是你唯一的方法就是花时间去定位代码,而如果你写下:
    const int MaxBufferLen = 50;
    这样的语句时,如果编译出错的话,MaxBufferLen字串就会出现在你的编译信息中,因为MaxBufferLen是被写入了符号表中的,这样就为你节省了不少时间.
    这里书上提到了一个注意点就是关于class的专属常量问题,你可以这样定义一个类的常量来保证一个类有且仅有一个此常量实体:

    class GamePlayer{    private:        static const int NumTurns = 5; //常量声明式        int scores[NumTurns];          //使用该常量        ...    };


    这样写只要你不取该常量地址你就可以只声明而无须提供定义式地用它们了,但是有些老编译器不支持这种写法,它要求常量必须给出定义式或者你要用到该常量的地址,那么你就必须在其对应的实现文件(非头文件)中提供定义式:
    const int GamePlayer::NumTurns;
    由于该常量在声明的时候已经提供初值了,所以定义时不可以在提供初值,而老式编译器则不支持在声明常量的时候赋初值,所以我们可以用"the enum hack"补偿法来实现:
class GamePlayer{    private:        enum { NumTurns =5};        int socores[NumTurns];        ...    };


    请记住:
    ■ 对于单纯常量最好用const,enum替代#define.
    ■ 对于形似函数的宏(macros),最好用inline函数代替#define.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4236149>

三、const

(1)const约束指针、迭代器
<span style="font-size:14px;">const char* p = "adc"; //non-const pointer,const datachar* const p = "abc"; //const pointer,non-const dataconst char* const p = "abc";//const pointer,const dataconst std::vector::iterator it = vec.begin();//(it的类型为 T* const)const pointerstd::vector::const_iterator it = vec.begin();//const data</span>


 
(2)const约束函数形参
void a(const T b);//保护形参不被修改void a(const T* b);//const pointervoid a(const T& b);//const reference</span>


另外只有引用的const传递可以传递一个临时对象,因为临时对象都是const属性, 且是不可见的,他短时间存在一个局部域中,所以不能使用指针,只有引用的const传递能够捕捉到这个家伙
 
(3)const约束函数返回
 

const T fun1();//这个其实无意义,因为参数返回本身就是赋值。const T* fun2();//调用时 const T *pValue = fun2();                          //我们可以把fun2()看作成一个变量,即指针内容不可变。T* const fun3(); //调用时 T* const pValue = fun3();                          //我们可以把fun2()看作成一个变量,即指针本身不可变。//引用类似


 
一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。原因如下:如果返回值为某个对象为const(const A test = A 实例)或某个对象的引用为const(const A& test = A实例) ,则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。
(4)const约束类
(1)const修饰成员变量
const修饰类的成员变量,表示成员常量,不能被修改,同时它只能在初始化列表中赋值
    class A    {        …        const int nValue;         //成员常量不能被修改        …        A(int x): nValue(x) { } ; //只能在初始化列表中赋值     }


(2)const修饰成员函数
const修饰类的成员函数,则该成员函数不能修改类中任何非const成员函数。一般写在函数的最后来修饰。
    class A    {        …       void function()const; //常成员函数, 它不改变对象的成员变量.也不能调用类中任何非const成员函数。}


 
对于const类对象/指针/引用,只能调用类的const成员函数,因此,const修饰成员函数的最重要作用就是限制对于const对象的使用
 
a. const成员函数不被允许修改它所在对象的任何一个数据成员。
b. const成员函数能够访问对象的const成员,而其他成员函数不可以

(3)const修饰类对象/对象指针/对象引用
 
·             const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。
 
·             const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。
例如:
class AAA{    void func1();    void func2() const;}const AAA aObj;aObj.func1(); //×aObj.func2(); //正确const AAA* aObj = new AAA();aObj-> func1(); //×aObj-> func2(); //正确


(5)使用const的一些建议
要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;要避免最一般的赋值操作错误,如将const变量赋值,;在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;形参为指针或者引用时最好使用const,避免形参为常量不能使用;不要轻易的将函数的返回值类型定为const,除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;任何不会修改数据成员的函数都应该声明为const 类型。

四、对象使用前初始化

例如
int x;  
在一些语境下会初始化为0,但在另一些语境下可能就不会初始化,例如
class Point{      int x,y; } 


如果使用未初始化的对象,可能会导致不确定的行为。所以,在使用对象之前,一定要将其初始化。
对于内置的数据类型,可以手工完成初始化:
int x=0; const char* text="A C-style string";  double d; std::cin>>d;//以读取input stream方式完成初始化

初始化效率往往高于赋值。赋值是先定义变量,在定义的时候可能已经调用了变量的构造函数,之后赋值是调用了赋值操作符。
而初始化是直接调用了复制构造函数
在一些情况下必须使用初始化方式,有些遍历在定义时就要求初始化,例如const和引用。
在C++的类继承中,基类先于派生类初始化。

下面还要讨论一下“不同编译单元内定义的non-local static对象”的初始化。
static对象的生命周期为从其构造出来到程序结束,在栈和堆上的对象显然不合符。static对象包括全局(global)对象(虽然没有用static修饰)、定义在namespace作用域内、在class、函数内、以及在file作用域内声明的static对象。
函数体内的static对象是local static对象,其他static对象是non-local static对象。
编译单元是指产出单一目标文件的源码,是源码文件加上其包含的头文件(#include files)。

现在有至少两个源码文件,每一个里面至少含有一个non-local static对象。如果一个编译单元的non-local static对象初始化使用了另外一个编译单元的non-local static对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义在不同编译单元内的non-local static对象”初始化次序无明确规定。
举个例子:现在有一个FileSystem class。由于某种需求,在global或namespace作用域内有一个non-static对象
class FileSystem{ public: …… std::size_t numDisks()const; …… }; extern FileSystem tfs;//定义在global作用域


现在一个客户新建一个类来处理文件系统内的目录,会使用定义在global作用域内的tfs
class Directory{ public:     Directory( params )     {         ……         std::size_t disks=tfs.numDisks();         ……     } }; Directory tempDir( params);  

如果tfs初始化晚于tempDir,那么tempDir会使用尚未初始化的tfs。tfs和tempDir是不同的人写的源码,定义在不同的编译单元,无法确定哪一个先初始化
一个改动即可消除上述问题。既然static对象只有一份拷贝,且只初始化一次,很容易想到“单例模式”。使用local static对象首次使用时初始化,返回其引用即可(local static声明周期是整个程序),以后再使用无需再次初始化。
class FileSystem{ public: …… std::size_t numDisks()const; …… }; FileSystem& tfs()                         {     static FileSystem fs;     return fs; }  class Directory{ public:     Directory( params )     {         ……         std::size_t disks=tfs.numDisks();         ……     } };  Directory tempDir() {     static Directory td;     return td; } 


这样修改之后,客户代码无需修改即可使用。相当于把对class的引用封装为接口,使用时才进行够走啊,节省成本,并确保class一定被初始化。
reference-returning函数往往简单,可是定义为inline函数。使用non-const static对象,在多线程环境下,会有不确定性。如果多个线程同时条用未初始化的reference-returning函数,会有“竞争形势(race conditions)”。消除的方法是:在单线程启动阶段手动调用这些函数

总结:为了避免使用未初始化的对象,要做三件事1、手动初始化non-member对象。2、使用初始化列表初始化member对象。3、消除初始化次序的不确定性。

来源: <http://blog.csdn.net/kangroger/article/details/41870771>
 

(二)、构造/析构/赋值运算

五、C++默认编写的函数


有人提出了一个问题:如果我写了一个空类,里面什么都没有,什么功能都没有实现,这样的类合法么?我能用它么?比如:
    class Null{};
    也许你也有相同的问题,而我的答案是----->当然合法,你完全可以像一般类一样用它.提出这个问题的人把我们今天讨论的话题给引导出来了:编译器到底默默地对我们自己写的类做了些什么,进行了哪些"暗箱操作"? 
    当你写了个新类时,你有可能忘记写它的构造函数、析构函数、拷贝构造、赋值函数,会不会编译不过?Don't worry! 编译器会为你提供这些函数的默认实现的.比如上面的Null类,编译器会自动将它转换为如下等价代码:

    class Null{    public:        Null(){...}        ~Null(){...}        Null(const Null& newNull){...}        Null& operator = (const Null& newNull){...}    };
    对于编译器默认构造函数,很多情况下它不会自动为你初始化内置型变量的值,这点很可怕,常常会导致一些莫名其妙的情况甚至是系统无迹象地崩溃,最好的做法还是老老实实的显式提供构造函数为上上之策,呵呵,对于一些非内置型变量你就必须提供它的默认构造函数,否则编译不过.
    提供的默认析构函数都是non-virtual,除非这个class的基类的析构函数为virtual类型的.拷贝构造函数(copy constructor)其实和一般的构造函数具有相似的功能只不过它的参数是另一个该类的引用对象,它的初始化过程通过调用成员变量的copy constructor并传递右操作数(那个对象参数)的成员变量值来完成初始化过程的.我们来看下面这个例子,这里我写了个类BookEntry:
    class BookEntry{    public:        BookEntry(string& title,double price,string& author)        :title_(title),price_(price),author_(author){        }        ...    private:        string title_,author_;        double price_;            };    //test.cpp    BookEntry firstBook("Effective C++",65.0,"Scott Meyers");    BookEntry secondBook(firstBook); //copy constructor    BookEntry thirdBook=secondBook;  //notice: here call copy constructor too.
    到这里生成的三个对象保持了相同的数据拷贝,这里我们注意一下最后一条语句调用的是copy constructor还是对它进行assign操作符运算,这里不要认为是调用了assign操作符,因为编译器优化的结果,编译器不需要先初始化以后再调用assign来完成一个新对象的构造过程,它可以直接调用copy构造函数直接完成,如果连这点都不能做到的话,那你这款这编译器也够糟糕的了!呵呵.
    最后我们来谈一下赋值构造运算(copy assign operator),它的行为基本上和copy constructor如出一辙,到这里也许你会产生一个看似很有天分的想法:既然编译器为我们提供了copy assign operator的默认实现,那么我们就没有必要为自己的新类来实现这个操作了!这样看起来好像是省了我们不少麻烦,但是实际上没那么简单,我不得不提醒你一下:别把世界想的太美好!在有些情况之下你如果不去提供实现题而单单依靠他的默认实现的话,往往会带来一些连你自己都想象不到的问题,甚至有的连编译也通不过,这是为什么呢?一般而言只有当生成的代码合法且有机会证明它有意义的情况下,copy assign才表现的是良好的,倘若这两个条件中有一个不符合,那么作为编译器来说它会怎么做?当然拒绝这个操作了哦!下面我来举个简单的例子来说明这个问题:
    我还是用上面的这个BookEntry类,稍微修改一下来达到我们的目的,呵呵:
    class BookEntry{    public:        BookEntry(string& title,double price,...)        :title_(title),price_(price),...{                }        ...    private:        string& title_;        double price_;    };    BookEntry oldBook("prison break",25.5,...);    BookEntry newBook("Unix network programming",43.0);    newBook = oldBook; // compiler error: 'operator =' function is unavailable in 'BookEntry'


    哒哒哒哒,问题来了吧?书上解释的原因我看了好几遍也没看懂,又请教了高手才弄懂,那么我尽可能的来解释它,对于"内含reference"的类的copy assign运算符"=",newBook = oldBook;从这条语句上来看是说newBook对象成员值被修改为oldBook成员的值,而这里BookEntry的成员title_是一个引用,如何修改引用?这里的话编译器遇到了一个难题,因为修改引用可以用两中理解:一是将其重新reference一个新的对象,显然这样不符号C++语法,C++不允许"让reference改指不同的对象";二是修改其引用对象的值.原则上这两种可能都会出现,客户到底是哪一种意图呢?编译器不能做出判断,于是它两个肩膀一耸,无能为力,不去为其提供默认copy assign操作,那我们要用操作符'='怎么办?

    你必须自己定义copy assign操作的实现!

    到这里我要对copy assign操作中的特殊的情况进行总结一下,一是对于"含reference"的类,如果你想用'='操作符的话,不好意思,你得自定义它的实现体;二是对于"含const对象"的类,编译器的反应当然是一样的,修改const对象本来就是非法的;还有一种情况是如果基类base class中
operator=被声明为private那么编译器就拒绝为其子类derived class提供默认的copy assign操作实现体.这三条大家要注意一下.

    请记住(最后一条是我自己加的,呵呵):
    ■ 编译器可以暗自为class创建default构造函数,copy构造函数,copy assignment操作符以及析构函数.
    ■ 请一定要清楚编译器为我们做了什么?在一些情况下,不要去麻烦编译器为我们自动生成的东西,因为这些东西不一定就是我们想要的,compiler is not god!,我们最好自己去实现它们,believe yourself!


来源: <http://blog.csdn.net/scofieldzhu/article/details/4242459>
 

六、拒绝自动生成的函数

条款06:若不想使用编译器自动生成的函数,就该明确拒绝.
(Explicitly disallow the use of compiler-generated functionsyou do not want)

在条款05中,我们弄清楚了编译器为我们自动生成的函数,而在现实中,我们有时并不需要编译器为我们提供某些函数的实现,因为它们的出现会导致我们现实中的某些最基本的常识变的不合理,违背事物发展的基本规律和准则,显然这些都不是我们想要的.那怎么办?最好的办法就是让编译器去拒绝这种特定的机能,具体如何做呢?
    比如举个例子,某一公司里面的员工类Employee,你立刻会写下如下类似实现:

    class Employee{    public:        Employee(){}        Employee(int id,const string& name,...)        :id_(id),name_(name),...{        }        ...    private:        int id_;        string name_;        ...    };    //test.cpp    Employee employee1(60015207,"scofieldzhu",...);    Employee employee2(60012345,"xiaohewang",...);    Employee employee3(employee1); //legal? Line1:    Employee employee4;    employee4 = employee2; //legal? Line2:

    仔细思考Line1,Line2的代码,这样写合法么?Employee对象在公司里面应该是唯一的(absolute ID),不能对它进行拷贝构造和赋值操作,你应该很乐意看到编译器拒绝这个操作,而不幸的是这里编译器编译却通过了,为什么呢?就是我们上一款提到的编译器自动这个类提供了默认的拷贝构造和赋值运算,我们如何让编译器去阻止拷贝和赋值操作呢?我们这里提出了一种现在很常用的做法:去声明它们而不去实现它,编译器声明的函数都是public,而为了阻止编译器去暗自创建专属版本,你可以将这些函数声明为private,这样就阻止了编译器去调用它(外部对象不能访问私有成员函数).好,那么上面类可以这样去声明了:
    
class Employee{    public:        Employee(){}        Employee(int id,const string& name,...)        :id_(id),name_(name),...{        }        ...    private:        Employee(const Employee&);        Employee& operator = (const Employee&);                int id_;        string name_;        ...    };


    重新编译一下test.cpp,喔霍,compiler error:cannot access private member declared in class 'Employee'.我们可以再次进行优化一下完全可以写一个基类去完成类似功能:
    
    class Uncopyable{    protected:        Uncopyable(){} //only derived can generator object        ~Uncopyable(){}    private:        Uncopyable(const Uncopyable&); //prevent copy        Uncopyable& operator = (const Uncopyable&); //prevent '='    };


    我只要继承给类就可以了:
    
    class Empolyee:private Uncopyable{    public:    ...    };


    这样写完全可以行得通,任何尝试拷贝构造和赋值运算(包括成员函数和友元函数)的访问,编译器会尝试生成对应的操作,它就必须调用基类对应的copy and assign copy操作,而基类的操作是私有成员,编译器调用就会被拒绝,呵呵,不错的想法吧!这里Empolyee为什么要用private继承而不用我们习惯的public继承,要解释原因就超出我们今天要讨论的话题了,以后条款中我会详细给你介绍它们的.

    请记住:
    ■ 为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现.使用像Uncopyable这样的base class也是一种做法.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4244257>
 

七、多态基类声明虚析构函数

条款07:为多态基类声明virtual析构函数
    (Declare destructors virtual in polymorphic base classes.)
    
内容:
    一看到这个条款,你可能立刻就会提出你的疑问来推翻这个结论:如果不为多态基类声明virtual析构函数,会发生什么灾难?好,其实答案是:当delete掉一个指向子类对象的基类指针时,它的行为是不可定义的----------指向derived部分没有被销毁,析构函数也没有被调用而基类部分通常会被销毁,这样就造成了一个可怕的"局部销毁"对象,造成了资源泄露、数据结构被损坏、浪费调试时间的惨状!这肯定不是你想要的结局.当然解决的办法就是为base class析构函数加上virtual.
    如果class当中没有virtual函数,通常表示它并不意图被当做base class,而你为它强行加上virt-ual析构函数的话,可能会造成不好的后果.书上的一个例子是:

    class SpecialString:public std::string{ //std::string有个non-virtual析构函数    ...    };    //test.cpp    SpecialString* pss=new SpecialString("Impending Room");    std::string* ps;    ...    ps = pss;    ...    delete ps; //这里出现未定义错误!!!现实中*ps的SpecialString资源会泄露.
    有时我们为基类提供一个pure virtual析构函数并且提供它的一份定义,这样我们会方便很多,因为析构函数的运作发式是,derived类析构先调用然后自动调用base部分析构函数.例如有个类Shape想被作为基类来使用,那么可以这样定义:
    class Shape{    public:        virtual ~Shape() = 0;    };    Shape::~Shape(){}//implement
    好了,今天就到这里.
    
    请记住:
    ★ polymorphic base classes(多态基类)应该声明为一个virtual析构函数.如果class带有任何virtual函数,它就应该拥有一个virtual析构函数.
    ★ classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明vir-tual析构函数.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4245418>
 

八、不让异常逃出析构

条款08:别让异常逃离析构函数
    (Prevent exception from leaving destructors).
    
内容:
    一般而言,只要析构函数抛出异常,程序就会可能过早结束或出现不确定行为,C++不喜欢析构函数吐出异常!!那么我们如何避免呢?书上举的一个例子是:假设你使用一个class负责数据库连接:

    class DBConnection{    public:        ...        static DBConnection create(); //get a DBConnection omit parameter for simplicity        void close(); //close  DBConnection    };
    为了防止客户使用完DBConnection以后忘记调用close(),我们可以创建一个新类DBConn来管理这个对象,我们可以在这个新类的析构函数中调用该DBConnection对象的close();
    class DBConn{    public:        DBConn(DBConnection& db):db_(db){}        ~DBConn(){            db_.close();        }        ...    private:        DBConnection& db_;    };

    我们就可以这样来用它:
 
   {        DBConn connection(*(DBConnection.create()));        ... //自动析构时候调用close方法    }

    这里出现个问题就是如果db_.close();要是吐出异常怎么办?通常我们用以下解决方案:
    一是只要抛出异常就结束程序:
    DBConn::~DBConn(){        try{            db_.close();        }catch(...){            ....            std::abort();//结束程序,可以强制"不明确行为"死掉        }    }
    二是吞掉异常:
    DBConn::~DBConn(){        try{            db_.close();        }catch(...){            ...        }    }
    一般而言吞掉异常不是很好的处理发式,但总比为"粗暴的结束程序"或"出现不明确行为"担负代价和风险要好,这样即使程序遭遇了一个错误或者异常情况下都可以继续运行,在另一方面提高了软件的健状性.
    而这些解决方案都存在一个问题:客户不能对"close失败的异常情况"做出反应.为了解决这个问题,这里我们可以将独立出来一个新的close接口:
    class DBConn{    public:        DBConn(DBConnection& db):db_(db){}        void close(){            db_.close();            isClosed_ = true;        }        ~DBConn(){            if(!isClosed_){                //使用以上两种解决方案之一来进行解决.            }                    }        ...    private:        DBConnection& db_;        bool isClosed_;                    };
    这样我们来重新审理这段代码,一个新的接口close(),提供给客户调用,如果出现异常客户可以第一时间来进行处理,如果可以确定这里不会出现异常的话,也可以不处理,客户还可以选择不调用close(),放弃对可能出现的异常处理,选择让析构函数自动调用.这样的设计你不觉得更加的合理吗?呵呵

    请记住:
    ▲ 析构函数绝对不要吐出异常.如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉异常,然后吞下它们(不传播)或结束程序.
    ▲ 如果,客户需要对某个操作函数运行期间的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4250593>
 

九、不在构造和析构中调用虚函数

条款09:绝不在构造和析构过程中调用virtual函数
    (Never call virtual functions during construction or destruction.)
    
内容:
    我以一个书上的例子开始:一个正规一点的大型超市一般都有一个管理进货、卖出、订货等交易动作的管理系统,该系统必须维护一个交易操作记录,方便查询,故每进行一笔交易时,需要把此项交易信息log入数据管理系统进行存储,这里我们有了一个交易类Transaction,但交易有很多种类型所以我们log它们的时候需要virtual函数来实现,故你很容易写出如下代码:

    class Transaction{    public:        Transaction(){            ...            logTransaction(); //记录此笔交易        }        virtual void logTransaction()const=0;        ...    };    class BuyTransaction:public Transaction{    public:        virtual void logTransaction()const;        ...    };    class SellTransaction:public Transaction{    public:        virtual void logTransaction()const;        ...    };


    这样你开始写下这样的执行代码,然后compile它:
BuyTransaction buy; //error LNK2019: unresolved external symbol "public: virtual void __thiscall //Transaction::logTransaction(void)const " (?logTransaction@Transaction@//@UBEXXZ) referenced in function "public: __thiscall Transaction:://Transaction(void)" (??0Transaction@@QAE@XZ)


    晕,这啥玩意?是不是编译器坏了?呵呵,不要急.我们来看看是你的问题还是编译器的问题,首先我们来看看buy这个对象的构造过程,注意构造顺序是:先构造基类,然后才是子类,当执行基类的构造函数中的logTransaction()方法时,该调用哪个版本的logTransaction呢?编译器这个时候调用的是基类版本Transaction::logTransaction,因为这个时候子类对象压根还没有被构造出来,这个时候编译器认为当前的对象类型是base class而不是derived class,不仅仅virtual函数被解析至base class,就算使用运行期类型信息(runtime type information)比如:dynamic_cast和typeid,也会把它当做base class类型的.
    这样的解释能看懂了么?如果还是看不懂,那么我们来用反证法论证它最终调用的就是base的版本.假设基类构造函数调用子类版本logTransaction(此例子中为:BuyTransaction::logTransaction),那么此时的子类对象的成员变量还没有被初始化,而子类版本的logTransaction内部则就使用了未被初始化的变量,C++编译器不会容许你这么做的,原因很简单因为使用未初始化变量会承担"出现不明确定义"的巨大风险(条款:04).
    同样的道理,析构函数也不能用virtual函数,对象析构函数的顺序与构造的顺序相反,当调用析构基类的析构函数时,子类已经销毁,那么它就没有就会调用子类版本的virtual函数.
    现在我们已经知道不能在构造析构函数中调用virtual函数,那么如何避免这种现象的发生,这可不是一件好差事,例如你可以在你的构造函数中用一个中间函数,而此中间函数实现中却调用了virtual函数,这样的调发式编译器是检测不出来的.而如果这个virtual函数是pure virtual且没提供默认实现代码时,当你屁颠屁颠的运行这个程序,当这个函数被调用时,大多数执行系统会终止程序,弄的你丈二和尚摸不着头脑;而如果你这个virtual函数已经有了一份实现代码,那么该版本就会被调用,而你的程序也顺利地大步向前走,正当你准备高兴之余你却突然发现它调用的不是子类的版本virtual函数,而留下你一人在作痛苦状思考:为什么它会调用错误的virtual版本呢?要是你连"编译器调用了错误的函数版本"都没检测出来的话,那你就等着你自己的心理防线彻底崩溃吧?
    那我该这么办啊?我偏要达到我的目的,让基类执行子类的实现代码?这个问题有解决方案么?好,我们再来看一下这个问题,既然我们不能让基类调用子类virtual函数版本,那么我们可以把子类的virtual函数所要实现的功能转交给基类来完成不就可以了嘛,想法很好!具体做法是:将基类的virtual函数变为non-virtual函数,然后在子类的构造函数当中传递特定信息给基类,让基类来完成子类的功能.
    实现代码可以这样修改:

    class Transaction{    public:        explicit Transaction(const std::string& logInfo){            ...            logTransaction(logInfo);        }        void logTransaction(const std::string& logInfo)const{        }    };    class BuyTransaction:public Transaction{    public:        BuyTransaction(Parameters):Transaction(createLogString(Parameters)){        }    private:        static std::string createLogString(Parameters)const;    };


    今天就讲到这里了,打字真TNND累人!
    
    请记住:
    ■ 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层).


来源: <http://blog.csdn.net/scofieldzhu/article/details/4254609>
 

十、operator=返回*this的引用

条款10:令operator= 返回一个reference to *this
    (Have assignment operators return a reference to *this)

内容:
    令赋值操作符返回一个自身对象的引用,究其根本原因就是为了实现"连锁赋值",比如,你可以这样写:
    int x,y,z;
    x=y=z=15;
    类似的假如你要使你写的类能实现"连锁赋值",那么你就你的赋值操作符必须返回一个指向操作符的左侧实参,这个是你应该遵循的协议:

   class Interger{    public:        ...        Interger& operator=(const Interger& rhs){            ...            return *this;        }    };


    这个条款只是一个协议,你可以不遵循,但这样的写法已经为大多数内置类型和标准程序库提供的或即将提供的类型共同遵守,如果你没有一个好的标新立异的理由的话,还是随众吧.

    请记住:
    ◆ 令赋值(assignment)操作符返回一个reference to *this.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4254705>
 

十一、operator=处理自我赋值

条款11: 在operator= 中处理"自我赋值"
        (Handle assignment to self operator=.)

内容:
    我们在写operator= 函数实现时,要注意一个问题:要考虑对象自我赋值的情况,因为客户完全可以写下如下代码:

    Widget w;    ...    w=w;

    这样写完全合法,那么我们在写Widget::operator=(xx)的实现时,一定要考虑到这个问题,否则一些想象不到的问题就来"拜访"你了,呵呵,比如,现在有一个类Widget:
    
class Bitmap{};    class Widget{    public:                ...        Widget& operator=(const Widget& rhs){            delete hBitmap_;//hBitmap_可能为NULL,注意:delete 删除NULL指针是可以的,它会什么都不做.            hBitmap_ = new Bitmap(*rhs.hBitmap_);            return *this;        }    private:        Bitmap* hBitmap_;    };


    这里的rhs与*this如果要是同一个对象,会出现什么问题?也就是说两个对象的hBitmap_是同一个图片数据,当delete hBitmap_;被执行时,它们指向的同一个Bitmap资源被释放掉,紧接着执行下面一条语句就出现了问题,因为新产生的hBitmap_是一个无效对象(它占用的资源对象已经被释放掉了).程序继续往下跑的时候就会可能出现访问内存等问题.这个时候你注意到了这个问题,你开始修改代码:
    
Widget& Widget::operator=(const Widget& rhs){        if(this == &rhs){ //增加了判断条件            return *this;        }        delete hBitmap_;        hBitmap_ = new Bitmap(*rhs.hBitmap_); // throw possibel exception?        return *this;    }


    代码增加了逻辑判断语句,这样就解决了"自我赋值安全"问题,然而如果new Bitmap(*rhs.hBitmap_);这条语句出现了异常,那么hBitmap_就是一个不可预料值,这样就很可怕,导致了"异常性安全"问题的出现.
    往往让operator=获得了"异常安全性"却会自动获得"自我赋值安全"的回报,所以很多人把焦点放在了"异常安全性",而对于"自我赋值安全性"就放任其不管,于是他们想出这样的方案:在赋值之前先不去删除以前的对象.这样就写出
如下代码:
    Widget& Widget::operator=(const Widget& rhs){        Bitmap* holdBitmap_ = hBitmap_;         hBitmap_ = new Bitmap(*rhs.hBitmap_);        delete holdBitmap_;        return *this;    }

    这样的话,如果"new Bitmap(*rhs.hBitmap_);"抛出异常,左右操作对象都保持不变,保持了"异常安全性",而它也保持了"自我赋值安全性",这行得通,不过你要是考虑到效率问题的话,有一种注重"异常安全性"的替代方案将是你的更好的选择,它是通过"copy and swap"实现的(条款29将会做更详细的探讨):
    class Widget{    public:        Widget& operator=(const Widget& rhs){            Widget tempt(rhs); //make a source copy             swap(rhs);            return *this;        }        ...    private:        void swap(const Widget& rhs); //交换两个Widget对象数据        ...    };
    由于改实现要将产生一个参数的副本所以说我们可以通过"pass by value"来传递参数,我们再次改写代码:
    class Widget{    public:        Widget& operator=(Wiget rhs){            swap(rhs);            return *this;        }                ....    private:        void swap(const Widget& rhs); //交换两个Widget对象数据        ...    };

    这样的函数签名看起来虽然看起来让人不是那么舒服,虽然它为了巧妙的修改代码却牺牲了代码可读性,然而将"copying动作"从函数本体移至"函数参数构造阶段"却可令编译器有时生成更高效代码.
    
请记住:
    ◆ 确保当对象自我赋值时operator=有良好行为.其中技术包括比较"来源对象"和"目标对象"的地址、精心周到的语句顺序、以及copy-and-swap.
    ◆ 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4257843>
 

十二、复制对象要面面俱到

条款12:复制对象时勿忘其每一个成分
        (Copy all parts of an object)

内容:
    从条款05中我们知道编译器为你提供了一些默认copying函数,而在某些情况下,我们不想要编译器提供的版本,我们自定义copying构造函数和copying assign操作函数,这个时候你无形之中惹恼了我们的编译器,你不用它的东西,它很"生气",于是它就开始复仇:你的实现代码几乎出错时,它就是不告诉你.
    这里考虑一个类用来表现一个应用用程序事件类型Event:
 

   class Event{    public:        Event(int id):eventID_(id){        }        Event(const Event& rhs):eventID(rhs.eventID_){                    }        Event& operator=(const Event& rhs){            eventID_ = rhs.eventID_;            return *this;        }        ...    private:        int eventID_;    };


    这样的代码没有问题也很合理,接下来我们增加了一个变量DateTime来记录该事件发生的时间:
 
   class DateTime{...};    class Event{        .... //代码与上面相同    private:        int eventID_;        DateTime dateTime_;    };


    这里发生了什么事情?对了,在copying构造函数中忘记对成员变量dataTime_进行初始化,在copying assign中忘记对dateTime_进行复制了.这里明显存在了"局部拷贝"的现象,而大多数编译器对这种现象却"视而不见",即使处于最高级别的警告级别中,这就是编译器的"复仇":既然你不用我提供的函数,那么你自己写的代码出错,我也不告诉你.没办法了,编译器不告诉我们,我们这里只能靠我们自己的小心和仔细了:确保你在复制对象时,复制所有成员变量.当你增加一个新的成员变量时,要同时修改copying构造函数和copying assign运算符操作,如果你忘记编译器不会这时就不会仁慈地去提醒你的.
    在有继承关系情况下,这里需要你注意一种情况:当你写copying构造和copying assign操作符操作时,注意要复制base class的成员变量,比如有一个系统SystemEvent类:
    class SystemEvent:public Event{    public:        SystemEvent(int id,int priority):Event(id),priority_(priority){}        SystemEvent(const SystemEvent& rhs):Event(rhs),priority_(rhs.priority_){        //这里调用了base class的copying 构造函数        }        SystemEvent& operator=(const SystemEvent& rhs){            Event::operator=(rhs);  //调用base class的copying assign 操作函数            priority_ = rhs.priority_;            return *this;        }    private:        int priority_;    };

    现在你应该理解条款中"复制"的含义:(1)复制所有local成员变量;(2)调用base class内适当的copying 函数.OK,It's over!
    
    请记住:
    ★ Copying函数应该确保复制"对象内所有成员变量"及"所有base class 成分"
    ★ 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用但基类的copying函数可以以成员变量初始化列表的方式被派生类调用(转载者加)


来源: <http://blog.csdn.net/scofieldzhu/article/details/4259662>

(三)、资源管理

十三、对象管理资源

条款13:以对象管理资源
    (use objects to manage resources.)

内容:
    我们还是以例子来说明问题,假设我们使用一个图片资源的类Image:
    class Image{...};
    接下来我们需要一个工厂函数供应我们特定的Image对象:
    Image* createImage();//返回创建的Image对象的地址。
    这里我们有一个隐性的协议:如果客户使用该工厂函数来获得Image对象,当你使用完了后,你有责任去删除它。而如果违反了这个协议,那么显然造成了"资源浪费"等一些列问题,客户一般这样用这个对象:
    

   {        Image* pcurImage=createImage();        ...        delete pcurImage;    }


    看起来一切都很良好,但是有些情况下就会出问题:如果在"..."代码区段出现return,goto,continue等语句时,我们往往会忘记delete pcurImage;这句代码;如果执行到释放代码之前有异常抛出,我们就没有机会去释放申请的资源,...;潜在的危险太多,太可怕了,客户根本就不好去预期哪种情况会出现,现在该怎么办?我们这里推荐的一种解决方案是:运用对象来管理资源。为什么会想到把资源放到对象里面呢?由于对象超出它的作用域时候会自动调用析构函数,我们只要在析构函数中调用它的释放资源函数就可以了!想法不错吧?呵呵,而在用法上我们要注意两点:

  • (1)被管理对象创建的时候要立即被放入对象中,这种观点被称为"资源取得时机便是初始化时机"(Resource Acquisition Is Initialization;RAII);
  • (2)管理对象(managing object)运用析构函数确保资源被释放。

    标准库中auto_ptr就是对这种情形设置的特制产品,也就是"智能指针",下面我们来示范如何使用它:

    {        std::auto_ptr<Image> aptrImage(createImage());        ... //退出作用域的时候自动调用析构函数释放占用的资源    }


    怎么样?所有问题都解决了吧,但这里我们要注意一下auto_ptr的用法,它的copying 构造和copying assign操作
函数有点奇怪,看下面代码:
    {        using namespace std;        auto_ptr<Image > aptrImage1(createImage()); //aptrImage1指向createImage()返回的对象        auto_ptr<Image > aptrImage2(aptrImage1); //aptrImage2指向对象,aptrImage1指向null        aptrImage1 = aptrImage2;                 //aptrImage1指向对象,aptrImage2指向null        ...                                          }
    这里的经过copying构造和copying assign操作函数后,原自动对象管理器就失去了指向原先拥有对象的机会,而将此机会交给了目标自动管理对象,自己指向null对象。也就是说"auto_ptr管理的资源必须绝对没有一个以上的auto_ptr同时指向它",从另外一方面说auto_ptr也非管理动态分配资源的最佳工具。于是auto_ptr的替代方案出来了,它就是"引用计数型智慧指针"(reference-couting smart pointer;RCSP).其实就是在目标对象上引用计数机制,当引用计数为0时,说明没有其它对象在引用它,它就对目标对象进行资源回收,伪代码形如下:
    void xxx::release(){            if(--refCount == 0){            delete this;        }            }    TR1的tr1::shared_ptr就是个RCSP,你可以这么用它:    {        ...        using std::tr1;        shared_ptr<Image > sptrImage1(createImage()); //sptrImage1指向createImage()的返回值对象        shared_ptr<Image > sptrImage2(sptrImage1); //sptrImage2与sptrImage1指向同一个对象        sptrImage1 = sptrImage2;                   //同上        ...                                        //sptrImage1与sptrImage2与他们所指向对象被自动销毁。    }


    你看,这样的复制行为就正常的多了,是不是?呵呵,ok,it's over!
    
    请记住:
    〓 为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
    〓 两个常被使用的RAII classes分别是tr1::shared_ptrauto_ptr。前者通常是较佳选择,因为其copying行为比较直观。若选auto_ptr,复制动作会使它(被复制物)指向null.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4260360>
 

十四、资源管理中小心copying

条款14: 在资源管理类中小心copying行为
        (Think carefully about copying behavior in resource-managing classes.)

内容:
    在上一款中我们谈到,用资源管理类来管理动态分配的资源及如何运用auto_ptrtr1::share_ptr管理heap-based资源来展示这个观点的,而在实际编写代码中,我们要用的资源不一定是来自heap,那么这样的auto_ptr和tr1::share_ptr就不适合作为资源管理类,这个时候我们需要建立自己的资源管理类。
    书上举了一个例子是互斥器对象mutex,那么mutex资源管理类就可以这么写:
    class Mutex{...}; //资源mutex类

    class Lock{    public:        explicit Lock(Mutex* pMutex):pcurMutex_(pMutex){            lock(pcurMutex_);        }        ~Lock(){            unLock(pcurMutex_);        }    private:        Mutex* pcurMutex_;    };
    接下来这么用:
    Mutex m;    ...    {        ...        Lock autoLock(&m);//自动锁定mutex        ...               //区块结束时自动释放mutex    }


    如果对象被复制,会发生什么情况?
    Lock m1(&m);    Lock m2(m1);


    这个时候你可能采用两种做法:
    (1)禁止复制。如果你觉得这里的对象复制不合理的话就可以明确的禁止这种复制行为(将copy构造和copy assign operator操作设置为私有成员).代码就这样写:
    class Lock:private Uncopyable{
        ...
    };
    (2)对被管理资源作"引用计数"。这个和tr1::shared_ptr体现的观点是一样的。幸运的是由于shared_ptr可以自行指定删除器,那么代码我们就可以这么写:
    class Lock{    public:        explicit Lock(Mutex* pm):sptrMutex_(pm,unLock){            lock(sptrMutex_.get()); //锁住它的目标资源        }    private:        std::tr1::shared_ptr<Mutex > sptrMutex_;    };
    对于管理资源的"复制行为",我们也可能采取下面两种策略实现(即在copy构造函数和赋值运算函数实现体内所采取的策略):
    (1)可以针对所管理的资源进行拷贝,也就是说,当你复制资源管理器的时候,你就对被管理资源实现了"深度拷贝"。
    (2)转移底部资源的拥有权。此时资源的拥有权会从被复制物转移到目标物。auto_ptr其实说明的就是这个观点。
    好了,本条款讨论结束.
    
    请记住:
    ◆ 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为.
    ◆ 普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法.不过其它行为也都可能被实现.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4262923>
 

十五、资源管理类提供原始资源访问

条款15: 在资源管理类中提供对原始资源的访问
        (Provide access to raw resources in resource-managing classes.)
        
内容:
    使用资源管理类使我们在一定程度上能够摆脱对原始资源的访问而造成的诸多风险,但在有有些情况下,当我们需要直接对资源进行访问操作时候,管理类这样的封装无疑给我们开发带来了不便,我们来举个例子来说明这种情况.
    现在我们有个图片资源工厂方法createImageResource();现在我们用auto_ptr对其进行管理:
    auto_ptr<Image> sptrImageRes(createImageResource); 
    而现在我们要对这张图片进行去色处理,处理函数为int decolourize(Image* psourceImage)我们这样调用它:
    decolourize(sptrImageRes);//当然不对啦,类型不匹配嘛
    这里我们就需要这个图片管理类来提供一个接口,此接口能够提供实现我们直接访问资源功能.幸好auto_ptr提供了get函数来执行显式转换,即返回管理对象的内部原始的资源指针.
    decolourize(sptrImageRes.get());//get方法就是其提供的接口函数
    一般来说,管理资源类都提供用户对原始资源的访问接口,通常通过显式转换或者隐式转换达到目的.上面举的例子是通过函数接口执行显式转换来获得原始的资源指针,我们也可以通过重载操作符->或者*来执行隐式转换获得原始资源指针,例如tr1::shared_ptr与auto_ptr都重载了operator->operator*函数实现,我们就可以通过它们直接访问image提供的接口操作:
 

    class Image{    public:        bool loadDataFromFile(const string& filename);        void blacklize();        void whiteBalance();        ...    };        //test.cpp    sptrImageRes->loadDataFromFile("shania twain.jpg");    sptrImageRes->blacklisze();    (*sptrImage).whiteBalance();


    而在有些时候我们觉得我们需要直接对原始资源对象进行操作而不是对管理类进行操作(这样做感觉起来更加的贴近事实的感觉,是不是?呵呵),这个时候我们通常通过隐式转换函数来实现,这样就会更直观,我们可以这样写:
    class Image{    public:        operator HANDLE()const{            return imageHandle_;        }        ...    private:        HANDLE imageHandle_;    };


    那么我们就可以很轻松的写下如下代码:
    Image theImage;    HANDLE handle = theImage; //OK! perfect! hehe
    书上有这么一段话:是否该提供一个显式转换函数(例如get成员函数)将RAII class转换为其底部资源,或是应该提供隐式转换,答案主要取决于RAII class被执行的特定工作以及它被使用情况.最佳设计是坚持忠告:"让接口容易被正确使用,不易被误用".通常显式转换函数如get是比较受欢迎的路子,因为它将"非故意之类型转换"的可能性最小化了.然而有时候,隐式类型转换所带来的"自然用法"也会引发天平倾斜.

    请记住:
    ■ APIs往往要求访问原始资源(raw resources),所以每一个RAII class应该提供一个"取得其所管理之资源"的办法.
    ■ 对原始资源的访问可能经由显式转换或隐式转换.一般而言显式转换比较安全,但隐式转换对客户比较方便.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4263234>

十六、new-delete同型成对

条款16:成对使用new和delete时要采取相同形式
        (Use the same form in corresponding uses of new and delete.)

内容:
    我们来看下面这段简单的代码:

    class Phone{...};    //test.cpp    Phone* pPhoneArray=new Phone[4];    assert(pPhoneArray != 0);    ...    delete pPhoneArray;

    程序运行起来的好像也良好,但却这段代码无形之中却造成了"内存泄露",你现在注意到了最后一行代码出现了点问题:这里new出来的是4个Phone对象,而删除的时候却删除了第一个,而其它3个Phone占用的内存却没有机会被释放掉.最后一行应该改为:delete[] pPhoneArray;为什么是这样呢?我们来作一个简单的分析一下new和delete到底做了什么?
    一个new操作执行了:(1)为该对象分配内存(调用::operator new);(2)调用构造函数初始化local成员变.而当你执行了一个delete操作执行了:(1)调用析构函数,释放拥有的资源;(2)释放对象所占用的memory(调用::operator delete).这里我们的delete就遇到了一个头疼的问题:到底要删除的对象的内存有多大,是一个对象还是多个对象,将有多少个析构函数被调用?也就是说delete的指针是执行单个对象地址还是指向一个对象数组的?
    我们先来看一下单个对象与对象数组的内存分布状态示意图:   


    从上面我们可以看出来,数组对象内存中包含了一个指示该数组的大小的变量size,有了这个指示我们就能确定总共要释放的内存大小,那我们如何标识出来让编译器知道倪,我们这里采用给delete带个小标识符'[]',这样编译器就知道了它要释放的指针原来是一个对象数组,问题解决!呵呵.
    我们来举个例子:

    {        ...        using std::string;        string pstringMultiObject = new string[4];        ...        delete[] pstringObject;        string pstringSingleObject =new string;        ...        delete pstringSingleObject;        ...    }


    上面的代码显然现在很合理没有什么问题,如果下面两种情况发生,会出现什么情况?
    (1)没有对pstringMultiObject使用"delete[]"形式.
    (2)对pstringSingleObject使用"delete[]"形式.
    对照内存分布示意图,先开始分析第一种情况,其结果是未有定义,由于很少的析构函数被调用.即使是内置型类型如int(没有析构函数)也是未定义行为.第二种情况,其结果是编译器会读取单个对象的内存块当作一个size值来看待,然后再多次size调用析构函数,浑然不知它处理的内存块压根就不是对象数组,天啊,难以想象该操作会造成什么样的后果?
    从上面的讨论我们很容易得到一个简单的游戏规则:如果你new的时候使用'[]',那么你delete的时候也请使用'[]',反之你也不应该使用'delete []'.
    在某些情况下这个规则对于typedef将显得更加重要,比如:
    typedef std::string addressList[4];    std::string* paddressList=new addressList;    assert(paddressList != 0);    delete[] paddressList; //注意这里不是delete paddressList;
    这里的addressList是一个含4个元素的数组类型变量,故不能使用delete paddressList;为了避免诸类错误的发生,我们一般不使用typedef定义数组类型,代替它的是标准程序库的vector<string>类型.
    好了,今天就到这里了!
    
    请记住:
    ★ 如果你在new表达式中使用[],必须在相应的delete表达式中也使用[].如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[].

来源: <http://blog.csdn.net/scofieldzhu/article/details/4264214>

 

十七、独立成句的new对象放入智能指针

条款17:以独立语句将newed对象置入智能指针
        (Store newed objects in smart pointers in standalone statements.)

内容:
    假设现在我们需要一个函数去处理一个消息processMessage,它需要一个消息对象和一个消息处理函数,由此我们遵循"对象管理资源"的原则,写出如下代码:

    class WindowsMesssage{...};    int messageProcedure();    void processMessage(std::tr1::shared_ptr<WindowsMesssage> message,messageProcedure);

    现在我们就来调用这个函数:
    class PaintMessage:public WindowsMesssage{        ...    };    //test.cpp    processMessage(new PaintMessage,messageProcedure); 

    这样调用,显然出现了原则性的问题:第一个参数与函数原型签名的第一个参数类型不匹配,又由于shared_ptr构造函数是explicit所以说不能进行隐式转换,故这里我们这样写:
    processMessage(std::tr1::shared_ptr<WindowsMesssage>(new PaintMessage),messageProcedure);
    我们现在来关注一下,在真正调用processMessage之前,编译器准备工作做了哪些?其实它做了三件事情:

  •     (1)调用messageProcedure;
  •     (2)执行new PaintMessage;
  •     (3)调用shared_ptr构造函数
    这里的执行顺序是什么呢?这里C++的做法与Java和C#就不一样了,他们总是执行相同的调用顺序(Java和C#,而这里C++的做法似乎很"危险":它弹性很大,但是我们可以确定的是(2)肯定是在(3)之前被调用,原因很简单:因为(3)需要(2)产生的对象,假设要是出现这样的调用顺序:(2)->(1)->(3),而在执行(1)的时候出现异常,那么导致了产生的PaintMessage对象的指针丢失,那么该对象指针就不能被置入资源管理对象shared_ptr之中,现在我们面临的危险就已经很明显了:调用processMessage时可能就发生资源泄露,哇靠,那怎么办呢?方法还是有的,这里主要的问题就是参数准备的时候做了太多的步骤导致了由于调用顺序的问题引起了一系列的问题,那么我们可以通过分离语句达到减少函数调用之前的准备事情的执行步骤数量,你可以这样调用,用单独的语句块内执行智能指针的构造过程:

    std::tr1::shared_ptr<WindowsMesssage> sptrPaintMessage(new PaintMessage);    processMessage(sptrPaintMessage,messageProcedure);


    由于编译器对"跨越语句块的各项操作"失去了执行重新排列的自由,所以编译器不能在它们之间任意选择执行顺序.
    
    请记住:
    ■ 以独立语句将newed对象存储于(置入)智能指针内.如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4269545>
 

(四)、设计与声明

十八、让接口易用而不误用

条款18: 让接口容易被正确使用,不易被误用
        (Make interfaces easy to use correctly and hard to use incorrectly.)

内容:
    假设现在的你需要提供一些接口给你的客户去使用,而现在的你没有任何这个方面的经验,那么你就要考虑下面这些情况的发生:

  •     (1)你没有告诉你的客户该如何使用该接口;
  •     (2)你告诉用户你怎么使用这些接口的方法,但是当用户使用的时候遗忘了某一步骤(甚至几个步骤);
  •     (3)客户正确地按照你说的方法使用,但是却没有达到预期的效果.
    对于以上的任何一种情况都会导致客户得不到你最终想提供的功能,而作为接口的提供方你也承担了不可推卸的责任,为了避免或减少这种情况发生的概率,我们这里提出了接口设计应当遵循的基本原则:要让你提供给别人使用的接口容易被正确使用,不易被误用.而要开发出这样的接口,首先你必须要了解客户可能做出什么样的错误,比如我们先来看书上举的这个例子,假设你要设计一个用来表现日期的类Date:
    class Date{    public:        Date(int month,int day,int year);        ...    };    //test.cpp    Date oneDay(29,3,1995); //晕,月日颠倒了.应该是Date oneDay(3,29,1995);


    为了防止这样的错误(客户记错了参数的类型意义)发生我用类型系统(type system)来进行保护,于是你构造了三个类型:
    struct Day{    explicit Day(int dayValue):value_(dayValue){}    int value_;    };    struct Month{    explicit Month(int monthValue):value_(monthValue){}    int value_;    };    struct Year{    explicit Year(int yearValue):value_(yearValue){}    int value_;    };    class Date{    public:        Date(const Month& m,const Day& d,const Year& y);        ...    };    Date d(29,3,1995); //error,wrong type    Date d(Day(29),Month(3),Year(1995)); //error,wrong type    Date d(Month(3),Day(29),Year(1995)); //OK    Date d(Month(15),Day(29),Year(1995)); //wow,can't detected this


    最后一个没有检测出来,于是我们再次修改代码,重新设计类型,增加对其值的限定,我们就可以对Month作如
下修改:
    class Month{    public:        static Month Jan(){return Month(1);}        static Month Feb(){return Month(2);}        ...        static Month Dec(){return Month(12);}        ...    private:        explicit Month(int m); //prvent create object from outward         ...    };    //test.cpp    Date d(Month::Mar(),Day(30),Year(1995));


    哦也,这样的设计是不是比原始版本好多了,呵呵.
    预防客户错误的另一个方法是,限制类型内什么事可做,什么事不能做.常见的限制是加上const.如果忘记const用法的话,回过头去看看条款3的讲解,比如"以const修饰operator*的返回类型"可阻止客户因"用户自定义类型"而犯:
    if(a*b = c)...//perfect,compile error
    下面是另一个一般性准则"让types容易被正确使用,不容易被误用"的表现形式:"除非有好理由,否则应该尽量令你的types的行为与内置类型保持一致".客户已经知道像int这样的type有些什么行为,所以你应该努力让你的types在合样合理的前提下也有相同的表现.为了避免无端的与内置类型不兼容,真正的理由是为了提供行为一致的接口,这样的"一致性"更能导致"接口容易被正确使用".STL容器的接口十分一致,这使得它们非常容易被使用,例每个STL容器都有一个size的成员函数.
    前面我们讨论过的用管理类对象管理资源类的例子,现在我们再来看这个例子,我们来看那个工厂函数接口:
    Image* createImage();
    这里我们来讨论一下客户怎么去使用这个接口?客户应该先得到新对象的指针,然后开始进行对该对象进行操作,用完以后要取保删除掉它,如果客户遗漏了这一步骤就会发生资源泄露.而这里你"要求"客户去确保该对象的删除操作被完成无疑是增加了风险,客户很可能忘掉去做它.
    Image* pImage = createImage();    ...    //delete pImage;//这里忘记调用delete pImage    ...
    这里你很不服气,他(客户)忘记调用是他的责任,怎么能够怪我的接口不好呢?他没按照我的步骤来导致得不到最终的效果,这能怪我嘛?可气!呵呵,你说的貌似有道理,那么我们这里说明一点:导致用户用错或者遗漏你强调的必须步骤,可能是你的接口让客户用起来复杂,不方便导致的.这可就是你的问题了,你要让你的接口更加简单,更加适合客户调用,这样就会在一定程度上减少了客户由于误用该接口而失败的概率.
    我们先来解决上面的问题,我们这里可以将返回值类型为智能指针类型,因为管理类对象将释放资源操作从客户这边转移到你这边了,减少了客户的操作也就是减少了出错的概率,你说是不是?
    std::tr1::shared_ptr<Image > createImage();
    条款14我们也谈到了tr1::shared_ptr容许当智能指针被建立起来时指定一个资源释放函数绑定在智能指针身上.假设这里的释放函数为getRidOfImage那么我们就可以这样实现函数体:
    std::tr1::shared_ptr<Image> createImage(){        std::tr1::shared_ptr<Image> retVal(static_cast<Image*>(0),getRidOfImage);        ...        retVal=...;//让retval指向正确的对象        return retVal;    }
    
    请记住:
    ■ 好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质.
    ■ "促进正确使用"的办法包括接口的一致性,以及与内置类型的行为兼容.
    ■ "阻止误用"的办法包括建立新类型限制类型上的操作束缚对象值以及消除客户的资源管理责任.
    ■ tr1::shared_ptr支持定制型删除器.这可防范DLL问题,可被用来自动解除互斥锁等等.
来源: <http://blog.csdn.net/scofieldzhu/article/details/4270956>
 

十九、设计class犹如设计type

条款19:设计class犹如设计type
    (Treat class design as type design.)
    
内容:    
    今天我们讨论的这款对于现在的我来说深有体会,随着做的项目越来越多,我越来越感觉到一个设计非常优秀的class对于开发者来说是多么的重要,我一直都在思考和探寻着如何设计优秀的classes,写一个class出来我相信大家都能够写的出来,而设计出优秀的classes则是一项艰巨的工作,本条款中提到设计class就像设计types一样,为什么这么说呢?因为设计好的types是一项艰巨的工作.好的types有自然的语法,直观的语
义,以及一或多个高效实现品.在C++中,一个不良的规划下的class定义恐怕无法达到上述任何一个目标,书中提到了如果你想设计高效的classes你至少要考虑下面的几个问题,而这几个问题的答案导致了你的设计规范:
    ★ 新type的对象应该如何被创建和销毁?
    这个问题主要影响到你的构造函数、析构函数以及内存分配和释放函数设计.
    ★ 对象的初始化和对象的复制该有什么样的区别?
    这个是很多人容易忽略的问题,而规范的做法下你需要考虑这点,它决定了你的构造函数,拷贝构造,复制操作符的行为以及它们之间的差别,别混淆了"初始化"和"赋值",如果忘记了赶紧回过头看一下条款4.
    ★ 新type的对象如果被pass by value(以值传递),意味着什么?
    注意pass by valuepass by reference传递参数时候的区别,而在处理赋值操作符时注意到"自我赋值"的情况.
    ★ 什么是新type的"合法值"?
    考虑你的class必须维护的约束条件,你的成员函数的错误检查工作.它也影响函数抛出的异常.
    ★ 你的class需要配合某个继承体系么?
    考虑一下你的class需要被其它class继承,那会影响你的声明函数,尤其是析构函数是否为virtual(条款7).    
    ★ 你的新type需要什么样的转换?
    考虑一下你的新type是否需要提供隐式或者显示转换函数(条款15).
    ★ 什么样的操作符和函数对此新type而言是合理的?
    ★ 什么样的标准函数应该被驳回?
    ★ 谁该取用新type的成员?

    这将决定哪个成员为public,哪个为protected,哪个为private.它也帮助你决定哪一个class和/或functions应该是friends,以及将它们嵌套与另一个之内是否合理.
    ★ 什么是新type的"未声明接口"(undeclared interface)?
    它对效率,异常安全性(条款29我们将讨论这个问题)以及资源运用(例如多任务锁定和动态内存)提供何种保证?你在这些方面提供的保证将为你的class实现代码加上相应的约束条件.
    ★ 你的新type有多么一般化?
    或许你其实并非定义一个新type,而是定义一整个types家族.果真如此你就不该定义一个新class,而是应该定义一个新的class template.
    ★ 你真的需要一个新type吗?
    如果只是定义新的derived class以便为既有的class添加机能,那么说不定单纯定义一或多个non-member函数或templates,能够达到目标.
    
    这些问题不容易回答,所以定义出高效的classes是一种挑战.然而如果能够设计出至少像C++内置类型一样好的用户自定义(user-defined) classes,一切汗水便都值得.

    请记住:
    ■ class的设计就是type的设计.在定义一个新type之前,请确定你眼睛考虑过本条款覆盖的所有讨论主题.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4272389>
 

二十、常引用参数代替值传递

条款20:宁以pass-by-reference-to-const替换pass-by-value
        (Prefer pass-by-reference-to-const to pass-by-value.)

内容:
    先看一个例子,假设我们有一个Person类:

    class Person{    public:        Person(const string& name,const string& address)        :name_(name),address_(address){        };        virtual ~Person(){}        ...    private:        ...        string name_;        string address_;    };

    现在有一个Student类继承Person类:
    class Student:public Person{    public:        Student(...){...}; //omit parameters for simplicity        ~Student(){}        ...    private:        ...        string schoolName_;        string schoolAddress_;    };
    现在这里有一个函数函数要处理Person对象,其原型签名是这样:
    void queryPersonInfo(Person somebody);
    我们可以这样调用:
 
   ...    Person firstPerson("michael scofield","New York");    queryPersonInfo(firstPerson); // Label1    Student firstStudent(...);    queryPersonInfo(firstStudent); //Label2    ...


    上面这段代码中由于queryPersonInfo函数传入参数是一个对象,那么参数传递中发生什么事情呢?C++默认的参数传递方式是按值传递(pass-by-value),所谓按值传递就是函数调用过程中,产生了一个对象副本(调用该对象的copy构造函数产生),该副本才被真正用来传递给目标函数的,函数对参数的所做的动作改变的都是对象副本而原对象没有发生任何改变.
    如果我们用上面的参数传递方式,那么我们将有可能发生如下糟糕的事情:
    (1)如此的函数调用会带来效率问题.如label1代码中,对象副本的产生过程也是新对象local成员变量的产生过程(name_,address_,schoolName_,schoolAddress_四个对象发生拷贝构造),哦欧,没想到吧,仅仅的一个参数传递方式付出了如此高的代价,如果你的代码中一直都用的是这种参数传递方式,那么你的程序效率将大打折扣.
    (2)可能会发生"对象切割"(object slicing)现象.如label2代码中,函数原型中参数类型是Person,而这里你却传入一个类型为Student对象,由于它们之间的继承关系的作用,编译器可以把firstStudent当做一个Person来处理,这样的话,firstStudent的base class将被以pass-by-value进行传递,(1)所述将调用base class的copy构造函数创建原对象副本传入目标函数,而firstStudent具有的derived class的那些特性将被完全切割掉,仅仅留下一个base class,oh no,这太吓人了,这不是我想要的结果.
    为了避免上述两种情况出现,我们改变一下函数原型:
    void queryPersonInfo(const Person& thePerson);
    这种参数传递方式称之为pass-by-reference-to-const,这种方式在传递过程中不会有对象副本的拷贝构造发生,它传过去的就是原对象本身的引用,底层实现上,引用是通过指针来实现的,也就是传递过去的是原对象的指针,而(2)的情况也不会发生,因为引用或指针对象的参数可以引起多态性行为,也就是解决了"对象分割"引起的问题.通过下面这个例子我们就能够更加清楚的理解这一点.
    这里我们有一个图形窗口系统Window类:
    class Window{    public:        ...        std::string name()const;     //返回窗口名称        virtual void display()const; //显示窗口和它的内容    };


    假设这里有它的一个子类WindowWithScrollBars,它是带有滚动条的窗口系统:
    class WindowWithScrollBars:public Window{    public:        ...        virtual void display()const;    };


    这里有个函数用来打印窗口名称,然后显示该窗口,我们先用pass-by-value进行函数原型设计:
    void printNameAndDisplay(Window win){ //critical warning: object-slicing          std::cout<<win.name();        w.display();    }


    我们来调用它:
    WindowWithScrollBars windowbar;    printNameAndDisplay(windowbar); 


    上面这行代码就有可能发生对象分割(当然产生对象副本),这里传入的windowbar对象的WindowWithScrollBars的特化信息都会被切除,那么该函数输出的窗口名称就是base class的name,不是windowbar的名称,即该函数实际调用的display版本为子类Window::display,而不是WindowWithScrollBars::display.
    我们再用pass-by-reference-to-const进行函数原型设计:
    void printNameAndDisplay(const Window& win){        ...//同上    }


    调用该函数的代码也不变.这里参数传进来的对象是哪种类型,该函数就调用那个类型的display版本,这里调用的是WindowWithScrollBars::display.
    那么我们何时使用pass-by-value方式,何时使用pass-by-reference-const方式进行参数传递呢?大多数人都采用这样一个不成文的约定:对于内置型对象(如int),STL迭代器以及函数对象使用pass-by-value,而对于自定义类对则采用pass-by-reference-const的方式进行参数传递.
    好了,今天我们就讨论到这里.
    
    请记住:
    ▲ 尽量以pass-by-reference-to-const替换pass-by-value.前者通常比较高效,并可避免切割问题(slicing problem).
    ▲ 以上规则并不适合与内置类型,以及STL的迭代器和函数对象.对它们而言,pass-by-value往往比较适合.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4275817>

二十一、需要返回对象时候不要返回引用

条款21:必须返回对象时,别妄想返回其reference
    (Don't try to return a reference when you must return an object.)

内容:
    前面一款我们提到了pass-by-value方式参数传递的效率带来的一系列可能引发的问题,你可能对pass-by-reference-const的参数传递方式特别的'青睐',而现在我们需要在在一次函数调用中要返回一个对象,问题是我们将以何种方式返回这个对象,你这个时候迫不及待的告诉我你的方案:直接返回该对象的reference.嗯,我们先用你的方案去解决下面这个问题,假设现在我们需要处理有理数Rational类:

    class Rational{    public:        Rational(int numerator=0,int denominator=1);        ...    private:        int numerator_,denominator_;        friend Rational& operator* (const Rational& lhs,const Rational& rhs);    };    ...    Rational& operator*(const Rational& lhs,const Rational& rhs){        Rational result( lhs.numerator_ * rhs.numerator_, lhs.denominator_ * rhs.denominator_ );        return result;    }
    哦吼,问题来了,你返回的对象result是一个local对象,此对象在函数退出时候自动调用析构函数来销毁自己,不存在了,你现在却在返回一个不存在对象的引用,你就会陷入"未定义"的危地.你这时立刻就想到了一个解决方案:在heap上产生该对象不就问题解决了嘛,因为堆上的对象不会自动销毁自己(除非你手工调用delete操作).ok,那么我们再来改一下代码看这种方案是否可行:
    Rational& operator*(const Rational& lhs,const Rational& rhs){        Rational* result=new Rational(lhs.numerator_ * rhs.numerator_,                                     rhs.denominator_ * rhs.denominator_);        return *result;    }
    这样实现貌似很合理,其实这里还是存在一个问题:你new出来的对象何时被delete掉,谁负责执行delete操作.如果客户在使用这个类的时候写出如下代码:
    Rational x,y,z,w;    w=x*y*z; //operator*(x,operator*(y,z)); 
    这个时候operator*(y,z)对象的reference(其实就是指针)就会丢失,你就无法执行delete操作,那么这里明显就存在了内存泄露的风险.晕,这样也不行,这时聪明的你灵感又来了:用static修饰local对象.你这样想的理由可能是下面两点:(1)static对象常驻内存,一旦对象被创建,你就不能随意去销毁它,除非程序结束;(2)每次调用该函数只是修改该对象的值,该对象始终只会存在一个.听起来想法很不错,我们依然还是先修改代码,看这方案是否可行?
    Rational& operator*(const Rational& lhs,const Rational& rhs){        static Rational result;        ...        result=...;        return result;    }
    而现在我们的客户却一不小心写下了这样的代码:
    Rational a(2,3);    Rational b(4,5);    Rational c(4,7);    Rational d(7,8);    if( (a*b) == (c*d)){        ...    }

    晕,问题又来了:a*b与c*d返回的对象引用都是引用的同一个static对象,那么这样的比较就显的没有意义了,因为结果一直都是true,哎呀,方案又'破产'了,问题没解决,心情很郁闷?别急,其实这里的解决方案很简单:我们只需要直接返回该对象就行了,我们先看一下最新的代码:
    const Rational operator*(const Rational& lhs,const Rational& rhs){                    return result(lhs.numerator_ * rhs.numerator_,lhs.denominator_ * rhs.denominator_);            }

    呵呵,现在你看看所有问题不是都解决了嘛!当然,你需要承受operator*返回值的构造成本和析构成本,然而长远来看那只是为了获得正确行为而付出的一个小小代价,好了,该条款至此就over了!
    
    请记住:
    ◆ 绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象.条款4已经为"在单线程环境中合理返回reference指向一个local static对象"提供一份设计实例.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4278568>
 

二十二、成员变量声明为private

条款22:将成员变量声明为private
        (Declare data members private.)

内容:
    首先我们讨论一下成员变量声明为public的情况,我们先从语法一致性角度来分析,如果成员变量不是public,那么客户访问这些变量唯一的方法只能是通过访问成员函数,由于这个时候public接口内只有函数,我们的客户就不需要在打算访问class成员时迷惑地试着记住是否该使用小括号(每个方法调用都会有括号),因为客户调用的只能是函数,每样别的形式,在客户使用你的class,这样就节省客户使用你的class时间(他/她省去了这些思考的时间).
    呵呵,上述的一致性的理由可能不会另你信服,那我们从另一个角度来描述这样做的理由:将成员变量说明为private可以控制对成员变量的访问控制属性(read-only,write-only,read-write-only).我们来举个例子来说明比较形象一点,这里我们有个Person类:

    class Person{    public:        Person(const string& name,unsigned char age,const string& address)        :name_(name),age_(age),address_(address){}        string getName()const{            return name_;        }        string setAddress(string address){            address_ = address;        }        void setAge(unsigned char age){            age_ = age;        }        unsigned char getAge()const{            return age_;        }    private:        string name_;   //read-only        unsigned char age_; //write-read-only        string address_; //write-only    };


    看了上面这个例子,是不是觉得这样做是有道理的,有些认可我的观点了?为了让对这个观点确信不疑,我不得不拿出我的'绝招'了:封装性.假如你声明成员变量为public,那么任何对象都可以访问它们了,这样毫无封装性可言,给代码的维护带来了很大的困难(成员变量值的改变可能是任何拥有可以随意修改它们值权限的地方).让客户通过成员函数访问(而不是通过直接访问变量),这样另一方面也隐藏了实现,变量成员的任何变动都不会影响接口(对外暴露的成员函数),客户不需要关心接口内部对成员变量到底做了些什么.
    不能将成员变量声明在protected内的论点也十分相似."语法一致性"和"细微划分之访问控制"等理由也适合于protected数据,但是对于封装性呢?protected成员变量封装性是不是高过public成员变量?这里的答案却不是肯定的.
    封装性到底跟什么有直接或者间接的关系呢?我们将在条款23中谈到,成员变量的封装性与"当其内容改变时可能造成的代码破坏量"成反比,这里的成员变量改变也学就是从class中移除它.
    现在假设我们已经有了一个public成员变量,现在我们取消它,我们的代码将有多少被破坏呢?所有使用它的代码都会被破坏,那是一个不可知的大量的修改工作;假设我们有一个protected成员变量,现在移除它,那么所有它的derived classes都会被破坏,同样也是一个不可知的大量的修改过程,由此这里我们得出一个结论:protectecd成员变量就像public成员变量一样缺乏封装性.因为在这两种情况下,一旦成员变量被修改,都会有不可预知的大量代码将会被破坏.
    好了,this topic is over!
    
    请记住:
    ■ 切记将成员变量声明为private.这可赋予客户访问数据的一致性,可细微划分访问控制,允诺约束条件获得保证,并提供class作者以充分的实现弹性.
    ■ protected并不比public更具封装性.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4281989>

二十三、用非成员函数和非友元函数替换成员函数

1、non-member方法与member方法没有本质区别,对于编译器来说,都是non-member方法,因为member方法绑定的对象,会被编译器转化为non-member方法的第一个形参。non-member方法与member方法唯一的区别是:member方法封装性更差,因为它可以访问private成员

2、根据面向对象的要求,数据与方法应该和对象捆绑在一起,这意味着应该使用member方法。其实,这个建议是错误的。为什么?

3、首先,non-member、non-friend方法提供更大的封装性。

4、其次,考虑下面的需求,我只需要类中的一个方法。如果是member方法,必须把整个class定义包含进来,即使其他的接口我不使用。如果使用non-member方法,我只需要包含需要的方法声明就好了。因此,non-member降低编译的依赖关系。举例来说,member方法,需要一点东西也要把整个class包含进来,而整个class中又关联其他东西,导致当前需要的东西与其他东西的依赖。如果是non-member方法,相当于把整个class分成一个一个小块,需要那个小块,就包含哪个小块。这是因为class 的定义不能跨越多个源文件,而namespace可以跨越多个源文件。

来源: <http://www.cnblogs.com/nzbbody/p/3543414.html>
 

二十四、参数需要类型转换应使用非成员函数

条款24:若所有参数皆需类型转换,请为此采用non-member函数
(Declare non-member functions when type conversions should apply to all parameters.)

内容:
    平时在我们的coding过程当中,我们有时候会碰到这样一些情况:我传进去的实参与声明函数的形参并不是同一类型的,函数调用竟然能够成功!这个听起来很不可思议是不是,那么我这里给你说一个编译器的"秘密",也许你在听完以后,你或许有可能对你说的那些"不可思议的事情"就不以为然了.
    我在这里要说的"秘密"就是:当函数的实参类型与形参的出现不一致时候,编译器会尽它最大的能力去寻找合理类型转换方案进行隐式转换,使它们之间的类型达到一致,从而完成函数的调用过程,当然如果最后还是没有找到合适的类型转换策略,编译器就会给出"类型不匹配"的编译错误.不怎么理解?好,下面我来举个例子来说明这个问题.
    假设现在有一个内置类型int的代理类IntDelegate,当然它就有很多特性,而这里我们要说的是它的乘法操作,你自然就写出了如下的代码:

    class IntDelegate{    public:        IntDelegate(int value = 0):intValue_(value){}        IntDelegate(const IntDelegate& rhs){            intValue_ = rhs.intValue_;        }        const IntDelegate operator*(const IntDelegate& rhs){            IntDelegate result;            result.intValue_ = intValue_ * rhs.intValue_;            return result;        }        int getValue()const{            return intValue_;        }        ...    private:        int intValue_;        ...    };


    wow,写的不错,下面我们来看看有没有什么问题,怎么去看?晕,"实践是检验真理的唯一标准",写代码测试一下:
    //test.cpp    ...    IntDelegate a(1),b(2);    IntDelegate c = a*b; //ok.    c = a * 2;           //Line1: ok.    c = 2 * a;           //Line2: error,喔欧,乘法交换律失败罗!shit!


    我们先来看Line1,你这里奇怪了?咦?这里的2压根就不是IntDelegate类型,怎么也能编译的过?请注意IntDelegate的构造函数是一个非explicit构造函数,这也就意味着编译器可以对它进行隐式转换,这种转换等价代码如下:
    IntDelegate tempt(2);    c = a * tempt;


    Line1现在该理解了吧?呵呵,我们接着来看Line2,这里的2为什么不能拥有上面的隐式转换呢?原因很简单:只有位于参数列表内的参数才隐式转换的合格参与者.显然这里的a才是那位合格者,呜呜....,不公平,我需要两个都能够进行转换.喔,问题不大!你想想,既然只有作为参数列表才能进行隐式转换,我们这里就很自然地想到把2也作为参数列表中的一员,这样不就搞定了嘛,呵呵!我们来修改一下代码,这里我们就需要变换一下操作符*的实现函数的函数原型:
    const IntDelegate operator*(const IntDelegate& lhs,const IntDelegate& rhs){        return IntDelegate(lhs.getValue()*rhs.getValue());    }


    在运行一下上面的测试代码,全部通过,结果完全真确,恭喜你!

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

来源: <http://blog.csdn.net/scofieldzhu/article/details/4293203>
 

二十五、没有异常的swap函数

条款25:考虑写出一个不抛异常的swap函数
(Consider support for a non-throwing swap.)

内容:
    swap函数就是相互交换两个对象的内容,标准程序库中已经提供了该函数的实现版本,其实现过程也是那么的如你预期所愿:

    namespace std{        template <typename T>        void swap(T& a,T& b)        {            T tempt(a);            a = b;            b = tempt;        }    }


    但是这种实现版本对某些类型来说,效率上存在很大的问题,尤其面临那些对的性能要求很高的软件,这种实现版本显然不能满足要求.这里我们所说的'某些类型'大体上指的是'以指针指向一个对象,内含真正数据'那种类型,这种设计的常见的表现形式就是所谓的"pimpl"手法(条款31将提到).我们再来看一个例子:
    class Window{ //这个class使用了pimpl手法    public:        Window(const Window& rhs);        Window& operator=(const Window& rhs){            ...            *pImpl_ = *(rhs.pImpl_);            ...        }        ...    private:        class WindowImpl{        public:            ...        private:            string title_;            std::vector<Window* > childList_;        };        WindowImpl* pImpl_;    };


    如果用std::swap进行对两个Window对象进行交换的话,那么它不止复制三个Window还复制了三个WindowImpl对象,非常缺乏效率.通常我们不能够(不被允许)改变std命名空间内的任何东西,但可以为标准templates制造特化版本,这里我们可以为std::swap提供一个全特化版本来提高效率:
    namespace std{        template<>        void swap<Window>(Window& a,Window& b){            swap(a.pImpl_,b.pImpl_);        }    }


    但是这里遇到的问题是pImpl_是private域内成员变量,我们无法直接访问它,怎么办?我们的做法是构造一个memberswap的版本.
    class Window{    public:        ...        void swap(Window& other){            using std::swap;            swap(pImpl_,other.pImpl_);        }        ...    };    namespace std{        template<>        void swap<Window>(Window& a,Window& b){            a.swap(b);        }    }


    倘若这里的Window以及WindowImpl要都是class template而非class:
    template<typename T>    class WindowImpl{...};    template<typename T>    class Window{...};


    那么我们就不能这么写了:
    namespace std{        template<typename T>        void swap<Window<T>>(Window<T>& lhs,Window<T>& rhs){            a.swap(b);        }    }


    因为我们企图偏特化一个function template,但C++只允许对class template偏特化,在function template身上偏特化是行不通的.那这么办?没有其它方法了吗?有!当你打算偏特化一个function template时,一般做法是简单地为它添加一个重载版本,比如:
    namespace std{        template<typename T>        void swap(Window<T>& lhs,Window<T>& rhs){ //这里是一个重载版本(没有<...>)            lhs.swap(rhs);        }    }


    这样重载function templates没有问题,但std是个特殊的命名空间,你可以全特化std内的template,但你不能添加心的template到std中,因为改命名空间内容完全由C++标准委员会决定,标准委员会禁止我们膨胀那些已经声明好的东西.晕死,这也不行,那我们就自己创建命名空间不就行了:
    namespace WindowStuff{        ...        template<typename T>        class Window{...};        ...        template<typename T>        void swap(Window<T>& lhs,Window<T>& rhs){            a.swap(b);        }    }


    那接下来我们的客户如何使用呢?假设你正在写一个function template,其内需要置换两个对象值:
    template<typename T>    void doSomething(T& obj1,T& ojb2){        using std::swap;        ...        swap(obj1,obj2);        ...    }


    一旦编译器看到对swap的调用,它们便查找适当的swap并调用.如果T是Window并位于命名空间WindowStuff内的,编译器会使用'参数依赖查找法'找到WindowStuff命名空间内的swap版本,如果该空间没有对应的swap版本,编译器就使用std::swap(using的声明式让std::swap在函数内曝光),而在std空间内如果你提供了针对T的特化版本,编译器就会挑中它,当然如果没有提供,那么编译器只有老老实实的调用那个一般化的swap版本的啰,呵呵.
    现在来说一下我们的条款内容:成员版swap绝不可抛出异常.那是因为swap的一个最好的应用是帮助classes(和class template)提供强烈的异常安全性保障.此约束只施行于成员版.
    今天说的有点多了,如果没看懂的话,请慢慢消化.
    请记住:
    ■ 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常.
    ■ 如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于classes(而非template),也请特化std::swap.
    ■ 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何""命名空间资格修饰.
    ■ 为"用户定义类型"进行std templates全特化是好的,但千万不要尝试在std内加入某些std而言全新的东西.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4294510>
 

(五)、实现

二十六、延后变量定义式

条款26:尽可能延后变量定义式的出现时间
    (Postpone variable definitions as long as possible.)

内容:
    由于定义一个类变量时,你就必须承担起构造和析构的负担.所以我们要尽量减少定义一些我们不用的对象,估计你现在对这条约束很不在意,我不用它为啥要定义它,我定义对象变量后肯定我会用它的嘛,呵呵,话不要说的太早有些时候,你的"不留心"会"出卖"你做出的这个承诺.
    比如下面这个例子,你需要加密密码,如果密码太短,抛出异常,否则返回加密后的版本:

    std::string encryptPassword(const std::string& password){        using namespace std;        string encrypted;        if(password.length() < MinimumPasswordLength){            throw logic_error("Password is too short");        }        ...         //开始加密        return encrypted;    }


    哒哒哒哒,问题出来了,这里的密码长度要是太多,那么抛出异常以后,该函数调用终止,而我们所定义的encrypted变量没有使用就开始析构了,浪费了我们辛辛苦苦构造出来它,如何改正呢?这个问题不大,我们延后它的定义式不就行了么,代码修改如下:
    std::string encryptPassword(const std::string& password){        using namespace std;            if(password.length() < MinimumPasswordLength){            throw logic_error("Password is too short");        }        string encrypted;        ...         //开始加密        return encrypted;    }


    问题解决!咦,貌似这里有一个小暇疵:encrypted的定义式表明它将调用default构造函数,而我们在大多数情况下需要立即给新定义变量赋初值(条款4),这种"赋值构造"比起"先构造在赋值"可以有效的提高程序运行效率,所以这里我们就提出一个约束:尽量延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参止.这样的话就不仅能够避免构造(和析构)非必要对象,还可以避免无意义的default构造行为.
    如果在循环语句中定义变量,我们将需要考虑到它的构造(析构)成本与赋值成本所承受成本的大小比较问题,看下面这两种形式:
    //A :变量定义于循环外
    Widget w;    for(int i = 0; i < n; i++){        w = ..;        ...    }


    //B:变量定义于循环内    for(int i = 0; i < n; i++){        Widget w(xxx);        ...            }


    两种形式的成本如下:

  •     A: 1个构造函数 + 1个析构函数 + n个赋值函数
  •     B: n个构造函数 + n个析构函数
    我们开始比较:如果Widget的构造析构成本比赋值成本要高的话,无疑A的做法总体效率要高;反之则B的做法效率高.
    请记住:
    ◆ 尽可能延后变量定义式的出现.这样做可增加程序的清晰度并改善程序效率.
来源: <http://blog.csdn.net/scofieldzhu/article/details/4296563>

二十七、少做转型操作

条款27:尽量少做转型动作
    (Minimize casting.)
    
内容:
    我们先来看一下转型语法,C风格的转型语法大概就是下面这种形式:

    (T)expression     //将expression转换为T类型</span>


    函数转型语法如下:
    T(expression)     ////将expression转换为T类型</span>


    这两种转型语法,我们称之为"旧式转型",既然是"旧式",那当然有"新式转型"啰,当然有,C++中提供了四种"新式转型",我大概将它们的适应的范围介绍一下(关于新式转换的详细的介绍,我在C++语言基础分类中的已经写了一篇,请注意浏览,呵):

  1.     ■ static_cast 用来进行强制隐式转换,我们平时遇到的大部分的转型功能都通过它来实现.例如将int转换为double,将void*转换为typed指针,将non-const对象转换为const对象,反之则只有const_cast能够完成.
  2.     ■ const_cast 用来将对象的const属性去掉,功能单一,使用方便,呵呵.
  3.     ■ dynamic_cast 用于继承体系下的"向下安全转换",通常用于将基类对象指针转换为其子类对象指针,它也是唯一一种无法用旧式转换进行替换的转型,也是唯一可能耗费重大运行成本的转型动作.
  4.     ■ reinterpret_cast 低级转型,结果依赖与编译器,这因为着它不可移植,我们平常很少遇到它,通常用于函数指针的转型操作.
    由于新式转型加入C++标准较晚,现在很多人都已经习惯用旧式转型,但随着你对新式转型的了解程度的深入,你就会发现新式转型与早有的旧式转型相比,有两大优点:(1)它很容易在代码中被识别出来;(2)由于各个转型动作的目标都有自己的适用范围,这就使得当你在用错的情况下,编译器能够诊断出你的错误.由于这两优点的存在越来越多的人已经从使用"旧式转型"阵营转投到"新式转型"阵营,这使得"新式转型"越来越受到程序员的青睐.
    当我们决定开始使用"新式转型"时,我们平时的一些看似很合理的习惯需要改正啰,最难改正的就数函数式转型了,看下面这个例子:
    class Widget{    public:        explicit Widget(int size);        ...    };    void doSomething(Widget& w);    doSomething(Widget(15)); //"旧式转型"中的函数转型    doSomething(static_cast<Widget>(15));//"新式转型"


    看到上面的"新式转型"的写法好像不像"生成对象",所以你很可能使用函数式转型而不使用static_cast.这里我要说的是当我们写下一段以后会导致出错的核心代码时,而在写下它们的稍后你往往会"觉得"通情达理,所以或许最好忽略你的感觉,始终理智地使用新式转型.
    请不要认为转型其实就是把某种类型视为另一种类型,任何一种转型动作往往真的令编译器额外地编译出运行期间执行的代,例如将int转型为double就会发生这种情况,因为在大部分的计算器体系结构中,int的底层表述不同于double的底层表述.这里我们还要提到一个经常被我们忽略的问题:单一的对象可能拥有一个以上的地址(例如:"以base*指向它"时的地址和"以Derived*指向它"时的地址),实际上一旦使用多重继承,这事几乎一直发生.即使在单一继承中也可能发生.恐怖!为什么会发生这样的事情呢?因为对象的布局方式和它们的地址计算发式随着编译器的不同而不同,这就以为着写出"根据对象如何布局"而写出的转型代码在某一平台上行得通,在其它平台上则不一定.往往这种错误的出现让很多有程序员甚至是一些很有经验的程序员眉头紧锁.
    关于转型的另外一件事情要注意的事情就是:我们很容易写出一些貌似很合理的代码,而往往运行的结果却不是我们想要的,比如我们来看下面这个段代码:
    class Window{    public:        virtual void onResize(){...}        ...    };    class SpecialWindow:public Window{    public:        virtual void onResize(){            static_cast<Window>(*this).onResize();//调用基类的实现代码            ... //这里进行SpecialWindow的专属行为.        }        ...    };


    这里会发生什么问题呢?static_cast<Window>(*this)这个转型动作并不是如你想象的那样得到当前对象的基类对象部分,其实编译器为你产生了的是----->基类对象的副本,喔欧,那就糟了,我执行的onResize方法压根就没有执行到基类对象上,SpecialWindow的专属onResize却执行在子类对象上,我使得这个对象处于一种"伤残"状态.shit!怎么办?你只有老老实实的这样改写代码:
    void SpecialWindow::onResize(){        Window::onResize(); //此时才是真正的调用基类部分的onResize实现.        ...     //同上    }


    知道随意做转型动作带来的危险了吧?呵呵.
    我们再来说另外一个问题,上面我们提到了dynamic_cast可能会耗费大量的转型成本,这句话怎么理解?因为dynamic_cast的许多实现版本执行速度相当慢.例如至少有一个很普遍的实现版本是基于"class名称之字符串比较",如果你在四层深的单继承体系内的某个对象身上执行dynamic_cast就可能耗用四次strcmp调用来比较class名称.深度继承或多重继承的成本更高,这里我就强调除了对一般转型保持机敏与猜疑,更应该在注重效率的代码中对dynamic_casts保持机敏与猜疑.如果你想要大量用dynamic_cast完成转型操作,请寻找其它策略进行避免它,这无疑会提升你的代码效率到一个可观的水平.
    今天说的太多了,打字也挺累的,希望大家能够理解我要表达的内容.
    请记住:
    ★ 如果可以,尽量避免转型,特别是在注重下来的代码中避免dynamic_cast.如果有个设计需要转型动作,试着发展无需转型的替代设计.
    ★ 如果转型是必要的,试着将它隐藏于某个函数背后.客户随后可以调用该函数,而不需将转型放进它们自己的代码内.
    ★ 宁可使用C++-style(新式)转型,不要使用旧式转型.前者很容易辨识出来,而且也比较有着分门别类的职掌.
来源: <http://blog.csdn.net/scofieldzhu/article/details/4300156>

二十八、避免返回对象内部数据的引用或指针

条款28:避免返回handles指向对象内部成分
    (Avoid returning "handles" to object internals.)

内容:
    这里所谓的handles一般包括内部对象的references、pointer、iterator,这条款是如何产生的呢?理由是什么呢?我先不必急着回答你这些问题,我们先来看一个例子,假设我们的程序中要用到矩形,于是我们写了Rectangle类去描述它,该类的内部对象为矩形左上角(upper left-hand corner)、右下角(Lower right-hand corner)两个Point对象,为了得到这两个内部对象数据我们提供了upperLeft与lowerRight函数接口.由于这里Point类是自定义类型,故我们先开始定义该类:

    class Point{    public:            Point(int x,int y);          ...        void setX(int value);        void setY(int value);        ...    };


    接下来我们开始写Rectangle类,考虑到让该类尽可能的小,我决定不把Point点定义在类的内部,而是通过在其内部放一个辅助类对象指针,该辅助类中存放两个Point对象.这种做法很常见,下面我们来实现:
    struct RectData{        Point upperLeftPoint,lowerRightPoint;            };    class Rectangle{    public:        ...        Point& upperLeft()const{ //返回内部对象数据handle            return pData->upperLeftPoint;        }        Point& lowerRight()const{ //返回内部对象数据handle            return pData->lowerRightPoint;        }    private:        std::tr1::shared_ptr<RectData> pData;    };


    一看代码我们就会很轻易的发现这里存在一个安全隐患:该类提供的两个接口返回的私有区域内部对象,那么客户使用该类的时候,有可能修改了该内部对象的数据,而实际上我们是不想让客户拥有这个权限的.例如:
    Point coord1(0,0);    Point coord2(100,100);    const Rectangle rec(coord1,coord2);    rec.upperLeft().setX(50);//wow, it changes the internal data,that's amazing!


    从这个例子我们至少可以了解以下两点:

  • (1)返回内部对象相当于让内部对象变为对用户可见即相当于让其处于public区段中;
  • (2)客户可以通过返回内部对象handle接口函数修改private对象属性,

这显然不是我们想要的结果.怎么解决?其中的一个解决方案就是在返回类型上加上const即可:

    class Rectangle{    public:        ...        const Point& upperLeft()const{return pData->upperLeftPoint;}        const Point& lowerRight()const{return pData->lowerRightPoint;}        ...    };


    问题似乎得到了解决,我们愿意让客户看到Rectangle的外围Points,所以这里是蓄意放松封装.更重要的是这是一个有限度的放松:这些函数只让出读取权,而修改权限仍然是被禁止的.在你说这个解决方案完全perfect之前,这时客户默默的写下了下面这段"恐怖"的代码:
    class GUIObject{...};//GUI base  class    const Rectangle boundingBox(const GUIObject& obj);//该函数返回GUI对象的外框.


    现在客户有可能这么用这个函数:
    GUIObject* pObject = NULL;    ... //create GUIObject object     const Point* pUpperLeft = &( boudingBox(*pObject).upperLeft());


    喔欧,看出问题了没有?boudingBox返回了一个临时对象对象(姑且称之为tempt),upperLeft返回的是tempt对象的内部数据的引用,那么现在pUpperLeft就指向该tempt对象的内部对象的地址.这里你别忘记啰:该语句调用结束以后tempt对象自动销毁,此时的pUpperLeft指向的就是一个不存在的对象.(这种空悬情况可以归纳为:构造出的堆对象无左值保存,从而造成空悬)pUpperLeft也就是变成空悬、悬挂了(dangling)!呵呵.
    今天我们就讨论到这里,如有什么问题的话,请留言.
    请记住:
    ■ 避免返回handles(包括reference,pointer,iterator)指向内部对象.遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生"虚吊号码牌"(dangling handles)的可能性降至最低.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4313586>

二十九、异常安全的努力

条款29:为"异常安全"而努力是值得的
    (Strive for exception-safe code.)
    
内容:
    看了这款的内容,我对C++的难以控制的"脾气"又有了进一步的了解,C++的安全性问题一直是广大非C++程序员所抨击C++语言"恶行"的主要方面,我们今天讨论的其"异常安全性"也是其复杂之处之一,看完这一款之后,你也许会发觉你以前写的代码可能会给最终产品带来多大的"风险隐患",废话我就不多说了,开始进入今天的话题.
    按照老规矩来,先看一个例子:假设有个class用来表现夹带背景图案的GUI菜单,这个class也要用于多线程环境当中,所以我们考虑用了一个互斥器(mutex)作为并发控制(concurrency control)之用:

    class PrettyMenu{    public:        ...        void changeBackground(std::istream& imgSrc); //改变图片背景        ...    private:        Mutex mutex_;       //互斥器        Image* bgImage_;    //目前的背景图像        int imageChangesCounts_; //背景图像被改变的次数    };


    下面是一个可能的实现:
    void PrettyMenu::changeBackground(std::istream& imgSrc){        lock(&mutex_);          //取得互斥器        delete bgImage_;        //摆脱旧的背景图像        ++imageChangesCounts_;  //修改变更次数        bgImage_ = new Image(imgSrc); //安装新的背景图像        unlock(&mutex_);        //释放互斥器    }


    从"异常安全性"的角度来看,这个真是个糟糕的实现版本,至少我们能够指出如下两点不足:(1)如果Image构造函数抛出异常,那么mutex_就永远不能得到unlock;(2)此外如果构造没有成功,而imageChangesCounts_是在构造之前执行,那么其自然就出现了"数据失真"的风险.
    解决问题(1)我们可以用对象管理资源方法进行解决(条款13中提到),我们只要把imageChangesCounts_的增操作放在对象产生之后也就可以解决问题(2)了,该实现先修改如下:
    void PrettyMenu::changeBackground(std::istream& imgSrc){        Lock(&mutex_);          //获得互斥器并确保它稍后被释放        delete bgImage_;                bgImage_ = new Image(imgSrc);         ++imageChangesCounts_;      }


    进一步我们可以把删除操作放在智能指针内部完成:
  
    class PrettyMenu{        ...        std::tr1::shared_ptr<Image> bgImage_;    };    void PrettyMenu::changeBackground(std::istream& imgSrc){        Lock(&mutex_);          //获得互斥器并确保它稍后被释放            bgImage_.reset(new Image(imgSrc));         ++imageChangesCounts_;      }


    代码是不是简洁多了,呵呵.看起来这样使得"异常安全机制"很完美嘛,不过"美中不足"的是:如果Image构造函数出现异常,那么有可能输入流imageSrc的读取记号将被移走.这样的话,该函数也只能提供"基本的异常安全保证".什么是"基本的异常安全保证",别急,挺我给你慢慢道来,作为异常安全函数必须提供以下三个保证之一:

  1.     ■ 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下.
  2.     ■ 强烈保证:如果异常被抛出,程序状态不改变.
  3.     ■ 不抛掷(nothrow)保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能.
    理解了上面三种专业属于后,我们来把目光再次聚焦到上面changeBackground的代码实现上,看看现在的代码,已经很好了呀,由于先前的那个小不足,使得我的这段代码还是没有达到"异常安全的强烈保证",我不放弃,四处寻找解决方案终于有个一般化的设计策略可以达到这个目的,这个策略被称为"copy and swap",俗话说"名如其人",这个原则其实就是:为你打算修改的对象保存一个副本,然后在该副本上修改.若修改过程中发生异常,问题不大,呵呵,因为原对象状态没有被改变嘛.修改动作完成以后进行"副本与原对象互换"操作.真是个不错的方案,我心理不由的赞一个,下面我们就开始改代码:
 
   struct PMImpl{        std::tr1::shared_ptr<Image> bgImage_;        int imageChangesCounts_;    };    class PrettyMenu{        ...        void changeBackground(std::istream& imgSrc){            using std::swap;               //哒哒哒哒,条款25我们谈到这个swap的实现方法的喔!            Lock m1(&mutex_);            std::tr1::shared_ptr<PMImpl> pNewImpl(new PMImpl(*pImpl_)); //make copy            pNewImpl->bgImage_.reset(new Image(imgSrc));//handle copy            ++pNew->imageChangesCounts_;            swap(pImpl_,pNewImpl); //swap         }    private:        Mutex mutex_;        std::tr1::shared_ptr<PMImpl> pImpl_;    };


    NND,代码量又增多了,有得必有失嘛,想开点!我们注意到copy-and-swap的关键在于"修改对象数据副本,然后在一个不抛异常的函数中将修改后的数据和原件置换",因此必须为每一个即将被改动的对象做一个副本,那得耗用你可能无法(无意愿)供应的时间和空间.这是一个很实际的问题:我们都希望提供"强烈保证";当它可被实现时你的确应该提供它,但"强烈保证"并非在任何时刻都显得实际.当强烈保证不切实际的时候,你就必须提供基本保证.在实际的开发当中你可以为某些函数提供强烈保证,但效率和复杂度带来的成本会使得你不得不去放弃它,万一实际不可行,使你退而求其次地只提供基本保证,任何人都不该因此责难你.对许多函数而言,"异常安全性之基本保证"是一个绝对通情达理的选择.
    累死我了,到此结束.
    请记住:
    ■ 异常安全函数即使发生异常也不会泄漏资源或允许任何数据结构被破坏.这样的函数区分为三种可能的保证:
基本型、强烈型、不抛异常型.
    ■ "强烈保证"往往能够以copy-and-swap实现出来,但"强烈保证"并非对所有函数都可实现或具备现实意义.
    ■ 函数提供的"异常安全保证"通常最高只等于其所调用之各个函数的"异常安全保证"中的最弱者.   
来源: <http://blog.csdn.net/scofieldzhu/article/details/4316680>

三十、inline里里外外

条款30:透彻了解inlining的里里外外
    (Understand the ins and outs of inlining.)

内容:
    对于该款的描述,原文中用了6页的篇幅进行阐述,这里我就将重要的知识点罗列出来,方便大家更好的理解这一条款.
    (1)inline函数的优缺点.
    优点:对于一个inline函数,你可以调用它们又不蒙受函数调用招致的额外开销,编译器就会对函数本体执行语境相关最优化,而大部分编译器不会对着一个非inline函数调用动作执行如此之最优化.
    缺点:由于对inline函数的每一个调用都以函数本体替换之,所以说这样就可能增加你的目标代码,在一台有限的机器上,过度热衷inlining会造成程序体积太大,即使拥有虚拟内存,inline造成的代码膨胀亦会导致额外的换页行为,降低指令高速缓存装置的击中率,以及伴随而来的效率损失.
    (2)inline只是对编译器的一个申请不是强制命令,该申请可以隐喻提出也可明确提出,隐喻提出就是将函数定义于class定义式内,这样的函数通常是成员函数或者是friend函数.明确声明就是在函数之前加关键字"inline".
    (3)inline函数通常一定要被置于头文件内,其在大多数C++程序中是编译器行为.
    (4)大部分编译器拒绝将太过复杂的函数inlining,而所有的virtual函数都不能inlining,因为virtual意味着"等待,知道运行期才确定调用哪个函数",而inline意味"执行前先将动作替换为被调用函数的本体".如果编译器不知道该调用哪个函数,你就很难责备它们拒绝将函数本体inlining.
    (5)有时候编译器inline某个函数的同时,还可能为其生成一个函数本体(比如程序要取某个line函数地址),值得一提的是,编译器通常不对"通过函数指针而进行的调用"实施inling,这就是说line函数的调用可能是被inlined,也可能不被inlined,取决于调用的实施方式.
    (6)"将构造函数和析构函数进行inling"是一个很糟糕的想法.看下面这段代码:

    class Base{    public:        ...    private:        std::string bm1,bm2;    };    class Derived:public Base{    public:        Derived(){} //空函数耶,够简单了吧?我想让它inlining,可以么?        ...    private:        std::string dm1,dm2,dm3;    };


    这个构造函数看起来很简单,inlining它应该没问题嘛,呵呵,但你的眼睛可能会欺骗你.我们来看它的等价代码:
        Derived::Derived(){ //"空白Derived构造函数"的观念性实现        Base::Base();//初始化"Base成分"        try{            dm1.std::string::string();        }catch(...){            Base::~Base();            throw;        }                try{            dm2.std::string::string();        }catch(...){            dm1.std::string::~string();            Base::~Base();            throw;        }        try{            dm3.std::string::string();        }catch(...){            dm2.std::string::~string();            dm1.std::string::~string();            Base::~Base();            throw;        }    }


    这段代码并不能代表编译器真正制造出来的代码,因为真正的编译器会以更精致复杂的做法来处理异常.
尽管如此,这已能准确反映Derived的空白构造函数必须提供的行为.
    (7)程序库设计者必须评估"将函数声明为inline"的冲击:inline函数无法随着程序库的升级而升级.
    我认为的该款的重点都罗列出来了,如果有什么不明白的地方,请留言.
    请记住:
    ■ 将大多数inlining限制在小型,被频繁调用的函数身上.这可使日后的调试过程和二进制升级更容易,
也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化.
    ■ 不要因为function templates出现在头文件,就将它们声明为inline.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4318150>
 

三十一、降低文件间编译依存关系

条款31:将文件间的编译依存关系降至最低
    (Minimize compilation dependencies between files.)

内容:
    在你们的开发团队中,一些有经验的工程师时不时地会教导新手一些基本的编程原则,其中"将接口从实现中分离"可能是他(她)要你必须牢记原则,因为C++并没有把它做的很好,这只能靠我们在平时的编写代码中注意这一点了,如果你不小心违背了这一原则,可能招致的后果就是:当你轻微的修改了某个类的实现,注意不是接口的时候,再次重新BUILD一次你的工程,Oh,My God!很多文件被重新编译和链接了,Build的时间大大超出你的预期,而这种事情的发生,估计你当时就会只恨编译器的BUILD的速度太慢,效率太低.呵呵.避免陷入这种窘境的一种有效的方法就是本条款要提出的内容:将文件间的编译依存关系降至最低.
    现在假设你写了一个Person类,一般你会这么构思你的代码:

    #include <string>    #include "date.h"    #include "address.h"    class Person{    public:        Person(const std::string& name,const Date& birthday,const Address& addr);        std::string name()const;        std::string birthDate()const;        std::string address()const;        ...    private:        std::string theName_;        Date theBirthDate_;        Address theAddress_;    };


    这样写显然在Person定义文件和其含入文件之间形成了一种编译依存关系(compilation dependency).可能就会导致开头我们提到的使你陷入窘境的情形出现.所以这里我们采取了另外一种实现方式,即将对象实现细则隐藏与一个指针背后.具体这样做:把Person类分割为两个类,一个只提供接口,另一个负责实现该接口.
 
   //person.h    #include <string>    #include <memory>    using std::string;    class Date;    class Address;    class Person{    public:        Person(const string& name,const Date& birthday,const Address& addr);        string name()const;        string birthDate()const;        string address()const;        ...    private:        struct Impl;        std::tr1::shared_ptr<Impl> pImpl_;    };    //person.cpp    #include "person.h"    struct Person:Impl{        Impl(const string& name,const Date& birthday,const Address& addr)            :theName_(name),theBirthDate_(birthday),theAddress_(addr){}        string name()const{            return theName_;        }        ...        string theName_;        Date theBirthDate_;        Address theAddress_;    };    Person::Person(const string& name,const Date& birthday,const Address& addr)    :pImpl_(new Impl(name,birthday,addr)){    }    string Person::name()const{        return pImpl_->name();    }    ...    


    以上这种设计常被称为pimpl idiom("pointer to implementation"),而这种class往往被称为Handle classes这样任何实现修改都不需要Person客户端的重新编译,此外由于客户无法看到Person的实现细目,也就不可能写出什么"取决于那些细目"的代码.这是真正的"接口与实现的分离"!这里的关键在于"声明的依存性"替换"定义的依存性",那正是编译依存性最小化的本质:现实中让头文件尽可能的自我满足,万一做不到,则让它与其他文件内的声明式相依.其他每一件事都源自这个简单的设计策略:

  •     (1)如果使用object references或object pointers可以完成任务,就不要使用objects.
  •     (2)如果能够,尽量以class声明式替换class定义式.
  •     (3)为声明式和定义式提供不同的头文件.(在模板类中常用到.)
    另一个制作Handle class的办法是,令Person成为一种特殊的abstract base class称之为Interface class.这种class只有一个virtual析构函数以及一组pure virtual函数,用来叙述整个接口.一个针对Person而写的Interface class或许看起来这样:
 
   //Person.h    ...    using std::string;    class Date;    class Address;    class Person{    public:        virtual ~Person();        virtual string name()const = 0;        virtual string birthDate()const = 0;        virtual string address()const = 0;        ...        static std::tr1::shared_ptr<Person>        create(const string& name,const Date& birthday,const Address& addr);    };    ...    //person.cpp    ...    class RealPerson:public Person{    public:        RealPerson(const string& name,const Date& birthday,const Address& addr);        virtual ~RealPerson(){}        string name()const;        ...    private:        string name_;        Date theBirthDate_;        Address theAddress_;    };    std::tr1::shared_ptr<Person> Person::create(const string& name,                                                const Date& birthday,                                                const Address& addr){        return std::tr1::shared_ptr<Person>(new RealPerson(name,birthday,addr));    }


    Handle classes和Interface classes解除了接口和实现之间的耦合关系,从而降低文件间的编译依存性.注意一点,两种class的实现方案带来的运行成本也是不容忽视的(由于篇幅问题,我就不具体阐述,相信你们也能自己分析的出来).如果你应该从你的实际出发,考虑用渐近方式去使用这些技术.
    请记住:
    ■ 支持"编译依存性最小化"的一般构想是:相依于声明式,而不要相依于定义式.基于此构想的两个手段是Handle classes和Interface classes.
    ■ 程序库头文件应该以"完全且仅有的声明式"的形式存在.这种做法不论是否涉及templates都适用.
来源: <http://blog.csdn.net/scofieldzhu/article/details/4324688>

(六)、继承与面向对象设计

三十二、确定public继承塑膜出is-a关系

条款32:确定你的public继承塑模出is-a关系
    (Make sure public inheritance model is "is-a".)

内容:
    我想对于类之间的public继承关系,每一本C++语言教程都会花费不少的篇幅去阐述,而本款所说的内容也许你早已是耳熟能详了,但在实际编码涉及中你是否从本条款出发去认真地考虑过你的设计的合理性呢?我觉得大多数的人没有真正做到这一点的.什么教"is-a"关系?如果你另class D以public形式继承class B,你便告诉编译器(以及你的代码阅读者)说,你D的每一个对象同时也是类型为B的一个对象.这听起来好像挺简单,但有的时候你必须注意你的直觉可能误导你.比如说,南极的企鹅其实是一种鸟类(这时你要是瞪大了疑惑眼睛就说明你生物没学好,呵呵),鸟是可以飞的,这是事实,要让你用C++描述这层关系的话,你毫不犹豫地写了出来,结果如下:

    class Bird{    public:        virtual void fly(); //bird can fly        ...    };    class Penguin:public Bird{    //Penguin is a kind of bird        ...    };


    嘟嘟嘟,这里遇到了点问题,按照这个继承体系来说企鹅可以fly,而我们知道现实当中的企鹅是不能飞的,晕,在你为你的不严谨的设计而成为了同行的笑柄之前,你略加思考就咔咔两下就改了你的设计,结果如下:
    class Bird{        ...//没有声明fly函数    };    class FlyingBird:public Bird{    public:        virtual void fly();        ...    };    class Penguin:public Bird{        ...  //没有声明fly函数    };


    这样的设计比原先的设计更能反映出我们真实的意思,多少为你挽回了一些面子,但是如果我们的软件系统可能根本就不需要区分会飞的鸟和不会飞的鸟,那你就不需要fly方法来提供,原先的"双classes继承体系"或许就相当令人满意了.这反映出这样一个事实,世界上并不存在一个"适用于所有软件"的完美设计.所谓的最佳设计取决于系统希望做什么事情,包括现在与未来.请不要去为程序所不关心的行为耗费你的脑细胞,这样不失为一个完美而有效的设计.
    令一种有思想的派系对于这种"企鹅是鸟但不会飞"的问题进行了下面的设计.
    void error(const std::string& msg); //定义于另外某处    class Penguin:public Bird{    public:        virtual void fly(){error("Attempt to make a penguin fly!");}    };


    呵呵,这里并不是说"企鹅不会飞",而是说"企鹅会飞,但尝试那么做是一种错误",当然这只有在运行期才能检测出来,其实为了遵循最佳设计原则,我们完全没有必要定义fly函数,就可以达到目的:
    class Bird{            ... //没有声明fly函数    };    class Penguin:public Bird{        ... //没有声明fly函数    };


    如果你这样用它:
    Penguin p;    p.fly();//喔欧,压根就没有fly方法,被编译器骂了吧!


    前面的条款18提到:好的接口可以防止无效的代码编译,因此你宁可采取"编译期就拒绝"的设计,而不是"直到运行期才侦测它们"的设计.好,我们再来看一个几何学方面的例子,我这里有个问题"请用类关系描述正方形与长方形之间的关系",这也太简单了吧?莎莎几声以后你的设计呈现出来了.
    class Rectangle{    public:        virtual void setHeight(int newHeight);        virtual void setWidth(int newWidth);        virtual int height()const;        virtual int width()const;        ...    };    class Square:public Rectangle{        ...    };


    正当你为你的设计而自鸣得意的时候,遇到了这样的一个客户,他(她)写出了下面这个函数和调用代码:
    void makeBigger(Rectangle& r){ //这个函数用以增加r的面积        int oldHeight = r.height;        r.setWidth(r.width() + 10); //宽度加10        assert(r.height() == oldHeight); //r的高度是否改变    }    //test.cpp    Square s;    ...    assert(s.width() == s.height());    makeBigger(s);    assert(s.width() == s.height()); //对所有正方形应该为真,但事实却不是


    怎么样?某些可施行与矩形身上的事情(例如宽度可独立于高度被外界修改)却不可施加于正方形身上(宽度和高度总是一样),但public继承关系主张能够施行与base class身上的每件事都能施行与derived class,在矩形与方形问题上该主张不能保持,故以public塑模它们之间的关系并不正确.

    请记住:
    ★ "public继承"因为is-a.适用于base class身上的每一件事情一定也适用与derived class身上,因为每一个derived class对象也都是一个base class对象.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4327174>
 

三十三、避免遮掩继承来的名称

  条款33:避免遮掩继承而来的名称
        (Avoid hiding inherited names.)    

    内容:

    相信大家对变量作用域的"名称遮掩"现象已经很熟悉了,比如下面这段代码:

    int x; //global variable    void someFunc()    {        double x;    //local variable        std::cin>>x; //read a new value to local x    }


    这时当编译器处于someFunc的作用域内并遭遇到名称x时,它先在本作用域中查找具有x名称的变量,如果找不到再到其他作用域找.但这里的两个作用域中的具有相同名称x的变量具有不同的数据类型(someFun中的为double而global中的为int),这不是需要在意的问题.C++的名称遮掩规则只关注名称的遮掩,而名称是否具有相同的类型并不重要.下面我们来重点看一下类中的名称遮掩问题.
    就像独立的普通函数一样,类中的函数也具有作用域的问题,先看下面这段代码:

    class Base    {    public:        virtual void mf1() = 0;        virtual void mf2();        void mf3();        ...    private:        int x_;    };    class Derived:public Base    {    public:        virtual void mf1();        void mf4()        {   //一个可能的实现            ...            mf2();            ...        }        ...    };


这段代码所构造的类作用域大体上可以用下面图示表示:

 当编译器执行在mf4中遇到mf2名称时候,开始在本作用域内(Derived作用域)寻找名称匹配,未果,开始在上一层作用域中(Base作用域中)查找,哦也,找到了,调用这个版本的mf2(Base::mf2).如果它没有找到,则它开始在可能的namespace中查找,还是没找到的话就到global区域中查找.
    现在我们在类中添加一下重载函数,看"名称遮掩"会怎么发生,新代码如下:

    class Base    {    public:        virtual void mf1() = 0;        virtual void mf1(int);        virtual void mf2();        void mf3();        void mf3(double);        ...    private:        int x_;    };    class Derived:public Base    {    public:        virtual void mf1();        void mf3();        void mf4();        ...    };


  这时候作用域的大概图示可以描述如下:

我们现在写一段测试代码去test!

    Derived d;    int x;    ...    d.mf1(); //no problem. call Derived::mf1    d.mf1(x); //error. Derived::mf1 hiden Base::mf1    d.mf2();  //no problem. call Base::mf2    d.mf3();  //no problem. call Derived::mf3    d.mf3(x); //error. Derived::mf3 hiden Base::mf3


这种结果是不是很出乎你的意料,shit! base class内的mf1和mf3的函数都被derived class内的相应的名字函数遮掩掉了,Base::mf1与Base::mf3竟然不再被子类继承,偶滴神啊,这种愚蠢的事情怎么能发生,快快阻止它吧!这问题不大,我们可以用using声明式达成目标,我们再来看代码:
    class Base{...}; //同上    class Derived:public Base{    public:        //让Base class内名为mf1与mf3的所有东西在         //Derived作用域内都可见(并且都是public)        using Base::mf1;        using Base::mf3;        ....//同上           };


重新test上面那段代码: 
喔也,一切ok!这意味着如果你继承base class并加上重载函数,而你又希望重新定义或覆写其中一部分,那么你必须为那些原本被遮掩的每个名称引入一个using声明式,否则某些你希望继承的名称会被遮掩.
    而有的时候你不想继承base classes的所有函数.在public继承中这显然不能发生,这违反了"base与derived classes之间的is-a关系.",然而在private继承下这是可能(条款39我们会详述).子类可以用using声明式来引用Base的函数.
    class Base    {    public:        virtual void mf1() = 0;        virtual void mf1(int);        ... //same as above    };    class Derived:private Base    {    public:        virtual void mf1()//转交函数(forwarding function)        {            Base::mf1();         }        ...    };    ...    Derived d;    int x;    d.mf1(); //good! call Derived::mf1    d.mf1(x); //Error as suppose,Base::mf1() was hidden.


    好了,今天的topic就over了!
    请记住:
    ◆ derived class内的名称会遮掩base classes内的名称.在public继承下没有人希望如此.    
    ◆ 为了让遮掩的名称再见天日,可使用using声明式或转交函数(forwarding functions).
来源: <http://blog.csdn.net/scofieldzhu/article/details/4346903>

三十四、区分接口继承和实现继承

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

          Differentiate between inheritance of interface and inheritance of implementation       

内容:

      当你开始设计你的class时候,有时候你希望它的Derived class只继承它的接口(声明);有时候你希望Derived class同时继承它的接口和实现,而且能够重新改写其继承的实现;有时候你又希望继承接口和实现的同时,但不能重新改写其继承的实现.以上这些情况当然在我们实际的开发中是很常见的,对于上述的选择我们该选择怎么样的class设计呢?

我们先看pure virtual 函数draw,这类函数的显著的特性是:它们必须被任何继承它们的子类重新声明和定义,它本身通常没有定义实现体,也就是说它存在的意义就是为了让derived class继承它的接口,注意我这里说的是通常它没有实现体,而你也可以为它提供实现体,C++对此也不会发脾气的,一般这种做法的用途很有限,主要是用在为子类提供一份默认的行为实现版本,稍后还要提到.为不同的函数分别提供接口可缺省实现可解决由于过度雷同的函数名称而引起class命名空间污染问题,其实我们可以用之前提到的"pure virtual 函数必须在derived classes中重新声明,但也可以拥有自己的实现"这一事实来解决掉这个缺点:

      non-virtual函数,声明它就是为了给子类继承接口及其的一份强制实现,由于non-virtual函数代表意义不变性凌驾于特异性,所以它绝对不该在derived class中被重新定义(我们将在条款36中重点讨论这类函数).

pure virtual函数、simple virtual函数、non-virtual函数之间的差异,使得你能以精确指定你要的子类继承的东西:只继承接口,或是继承接口和一份缺省实现,或是继承接口和一份强制实现.由于这些不同类型的声明意味着根本意义并不相同的事情,当你声明你的成员函数时,必须谨慎选择.

请记住:

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

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

■ impure virtual函数具体指定接口继承及其缺省实现继承.

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

来源: <http://blog.csdn.net/scofieldzhu/article/details/4350339>
 

三十五、考虑虚函数以外的选择

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

内容:
     假如现在你正在写一个游戏软件,游戏里面有各种游戏任务角色,人一多了嘛,就容易出现各个方面的利益冲突,而游戏设计者让他们解决冲突的直接方法就是--战斗,于是游戏中各种人物相互之间砍杀的画面就经常出现,这样就出现了由于受伤或者其它因素导致了健康系数的下降,这个时候作为游戏设计者的你,显然在这里要提供一个函数来计算各种人物当前的健康系数。这个难不倒你,由于各中人物的角色不同,身体素质不同等决定了它们的健康系数也不同,用virtual函数来计算看来是很合理的想法。

     class GameCharacter{     public:               virtual int healthValue()const; // 返回人物的健康指数并提供默认的实现体, 子类可以改写                ...        };


     这时候有个思想流派主张所有virtual函数都应当为private,其子类可以改写该函数,于是他们就提出了他们对这个问题的建议,让non-virtual成为public接口来提供游戏人物的健康指数,而在内部该函数调用private virtual函数(真正负责计算健康指数)
     class GameCharacter{     public:         int healthValue()const{              ... //调用之前准备工作              int value = calcHealthValue();              ...//调用之后的一些清理工作等         }         ...     private:         virtual int calcHealthValue()const{         ...         }         ...     };


     以上这种"令客户通过public non-virtual成员函数间接调用private virtual函数",称为non-virtual interface(NVI) 手法。它是所谓的Template Method涉及模式(与C++ templates并无关联)的一个独特表现形式,而这个public non-virtual(healthValue)称为virtual函数的wrapper。这种手法的好处是很明显:我们可以在"真正做一些工作"之前”做一些准备工作“,而在调用完成之后我们也具有”后期处理事情“的能力。而它的缺点也有,其中一点是该手法的应用范围,比如某些类继承体系要求子类的virtual函数必须调用基类的对应兄弟,为了让这样合法,virtual函数就必须是protected。而有时候甚至一定得是public(例如具备多态性的析构函数),这样依赖NVI就没法用了。
     于是我们的NVI替代方案又出来了,我们可以通过function pointer来实现strategy来解决本款刚刚提出的问题。
     class GameCharacter;     int defaultHealthCalc(const GameCharacter& gc);     class GameCharacter{     public:          typedef int (*HealthCalcFun)(const GameCharacter&);          explicit GameCharater(HealthCalcFun hcf = defaultHealthCalc)              :health_calculate_function_(hcf){          }          int healthValue()const{              return health_calculate_function_(*this);          }          ...     private:          HealthCalcFunc health_calculate_function_;        };


     这种手法比NVI提供了更多的弹性:它既可以为一种游戏角色提供不同的健康指数计算方法,如果你再提供像setHealthFun类似的接口以后,我们就可以在运行期间更改同一角色人物的健康指数计算方法。多好啊!先不要激动,这里好像有一个问题:这里的健康指数计算函数不需要去访问类的non-public成分,这样当然没用问题,但如果我们提供的健康指数函数需要访问GameCharater内部成分即非non-public的话,这就有问题了,难道我们不得不去让该函数成为GameCharater类友元或通过其它方式才能解决问题(这显然降低了该类的封装性)。当你面对这样问题的时候你就要重新考虑你的设计策略的优点是否足以弥补缺点而进行抉择了。
     上面这种基于函数指针的做法看起来是不是那么的死板和苛刻,呵呵,为什么一定要是一个函数指针呢?为什么不是一个“像函数的东西”呢?为什么这个函数就不能够是个成员函数呢?为什么函数一定要返回int而不是可被转换为int的类型呢?为了解决这些疑问,我们拿出了我们的重量级武器--tr1::function.先将刚才的设计修改一下:
     ...     class GameCharacter{     public:              typedef std::tr1::function<int (const GameCharacter& )> HealthCalcFunc;              .... //同上     };


     悠呵,这个typedef这么大的一块是个啥玩意啊?
     std::tr1::function<int (const GameCharacter& )></span>


      实际上这个签名代表的是"接受一个reference指向const GameCharaceter,并返回int".而我们定义的这个HealthCalcFunc可以保持任何与此签名式兼容的可调用物。这里的兼容指的是可调用物的参数可被隐式转换为const GameCharaceter&,而其返回类型可被隐式转换为int。下面我们来测试其效果:
      
      short calcHealth(const GameCharaceter&); //健康指数计算函数,注意它的返回类型为non-int      struct HealthCalculator{  //为计算健康而设计的函数对象              int operator(const GameCharaceter&)const{...}        };      class GameLevel{      public:              float health(const GameCharacter&)const; //为计算健康指数设计的成员函数              ...      }      //定义两种游戏角色      class EvilBadBuy:public GameCharaceter{              ... //同前面      };      class EyeCandyCharacter:public GameCharacter{             ... //同前面      };      EvilBadGuy ebg1(calcHealth);   //人物1 使用函数计算健康指数      EyeCandyCharacter ecc1(HealthCalculator());//人物2:使用函数对象计算健康指数      GameLevel curLevel;      ...      //人物3:使用某个成员函数计算健康指数      EvilBadGuy ebg2( std::tr1::bind( &GameLevel::health, curLevel, _1 ) );


      wow!!!这个trl::function也忒强大了吧!最后一个函数tr1::bind貌似生面孔,它是做什么滴呢?我们要知道GameCharacter::health实际上接受两个参数(第一个是隐式参数---该类的对象地址,即this,别忘记!)而HealthCalcFunc却只能接受一个参数,于是我们必须进行转换,所以我们使用curLevel作为该成员函数的第一个参数的值,让ebg2总是以curLevel对象的健康函数来计算,这就是tr1::bind的作用。"_1"意味着"当为ebg2调用GameLevel::health时以curLevel作为GameLevel对象".
       如果你对设计模式感兴趣的话,传统的Strategy模式做法会将健康函数做成一个分离的继承体系中的virtual成员函数,设计的结果图本来是要帖出来的,不过该论坛的贴图功能好像不这么完善,二者大多数人喜欢直接看代码,那么我就把骨干代码帖出来:
       class GameCharacter;       class HealthCalcFunc{       public:                ...                virtual int calc(const GameCharacter& gc)const{                          ...                }               ...         };        HealthCalcFunc default_health_calculator;       class GameCharacter{       public:                explicit GameCharacter(HealthCalcFunc* function = &default_health_calculator )                          :health_calculator_function(function){}                int healthValue()const{                          return health_calculator_function_->calc(*this);                }                ...       private:                HealthCalcFunc* health_calculator_function_;       };


        接下来我们来快速复习我们验证过的几个替代virtual函数的方案:
        ◆ 使用non-virtual interface(NVI)手法。
        ◆ 函数指针作为成员变量。
        ◆ 以tr1::function成员变量替换virtual函数。
        ◆ 传统的Strategy设计模式的实现手法。

        好,今天敲的够多的了!

        请记住:
        ■ virtual函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的template Method 设计模式。
        ■ 将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的 non-public成员
        ■ tr1::function对象的行为就像一般函数指针。这样的对象可接纳"与给定之目标签名式兼容"的所以可调 用物。


来源: <http://blog.csdn.net/scofieldzhu/article/details/4391105>
 

三十六、绝不定义继承的非虚函数

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

内容:
    我们来看个例子,class B为基类,class D为public继承自B的子类:

    class B{    public:        void func(){...}            };    class D:public B{...}


    接下来我们面对以下行为:
    D dObject;    B* basePtr = &dObject;    D* dOjbectPtr = &dObject;


    看下面这两种调用方式:
    basePtr->func();    dOjbectPtr->func();


    两个指针指向同一个对象,调用的同一个方法,得到的结果当然是一样的了。现在我要在子类D中重新定义func()函数,再次我们测试一下上面的两种调用方式,结果还是一样吗?这次的结果却出乎我们的意料,结果竟然不一样了:basePtr->func()调用的函数版本依然不变,而dOjbectPtr->func()居然调用了D::func版本。你这里可别犯迷糊,出现这样的结果的原因也是很显然--函数名"遮掩"现象(条款33,还记得吗?呵呵)。这样看来重新定义了non-virtual函数导致了D对象的"精神分裂",同一个对象调用同一个方法,产生不同的结果,这结果却依赖对该对象的reference类型或指向该对象的指针类型.
    如果这样的表述形式不能使你同意本条款的话,我也很乐意从理论层次对其进行解释:条款32中我们提到对于D的继承体系中,适用于B对象的每一件事情同样也适用与D对象,因为D与B是is-a的关系。我们将视线转回到这里如果D真的必须实现与B不同的func版本,而每一个B对象都必须使用自己的func版本,那么"每一个D都是B"就不为真,那么D就不应该public继承B.另一方面,条款34中我们提到non-virtual函数是"不变性凌驾与特异性之上",如果D真的需要实现不同的func版本的话,那么func就无法反映其"不变性凌驾与特异性之上"特性,既然这样func就应该声明为virtual。最后如果每一个D都是一个B,且如果func真的为B反映出"不变性凌驾与特异性之上"的特性,那么就不需要D重新定义func,而且它也不应该尝试这么做。
    不论哪一种观点,结论都相同:任何情况下都不该重新定义一个继承而来的non-virtual函数.
    请记住:
    ■ 绝对不要重新定义继承而来的non-virtual函数.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4402850>

三十七、绝不定义继承的默认参数值

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

内容:

     审视了一下条款以后,我们可以换一种说法:绝不重新定义继承而来的virtual函数或non-virtual函数的缺省参数值.而在上一款中我们提到,绝对不要试图去重新定义继承而来的non-virtual函数,将这句话与本条款合并的话,我们就可以将本条款的内容简化为:绝不重新定义继承而来的virtual函数的缺省参数值.为什么继承而来的virtual函数的缺省参数值不能被重新定义呢?其实原因也挺简单:缺省参数是静态绑定,而virtu
al函数是动态绑定. 至于什么是动态绑定与静态绑定,我想大家都应该清楚,不过在这里我还是要简单的唠叨一下,加强一下理解也不是件坏事,呵呵!所谓对象的静态绑定也叫前期绑定,它是说该对象类型和行为在程序编译期间就可以确定,例如:

     class Shape{     public:              enum Color{RED,GREEN,BLUE};              virtual void draw(Color color = RED)const = 0;              ...     };     class Circle:public Shape{     public:              //哦欧! 竟然改变缺省参数值              virtual void draw(Color color = GREEN)const{ ... }     };     class Rectangle:public Shape{     public:             //没用指定参数类型,需要用户去明确的指定其值            //静态绑定下不继承基类的缺省值,若以指针或引用调用则不需要指定缺省值,因为动态绑定            //继承基类的参数缺省值            virtual void draw(Color color)const{ ... }     };


     看一下下面几个指针:
     Shape* ps;     Shape* pc = new Circle;     Shape* pr = new Rectangle;


     这里的ps,pc,pr不管它具体指向的是什么对象,他们的静态类型都是Shape*.而动态类型就是它们真正指向的对象的类型.故pc的动态类型为Circle*,而pr的动态类型为Rectangle*,ps由于没用指向任何对象,所以此时没有动态类型. 好,关于这两种类型的特点及差异我们就讨论到这里,现在我们来看下面这条语句:
     pc->draw(); //注意调用的是: Circle::draw(RED)
     哦欧,偶滴个神呐!怎么会调用Circle::draw(RED),不伦不类的?为什么不是Circle::draw(GREEN)?别急,仔细分析一下,我们就会发现去解释这种"怪事"的出现原因也并不那么复杂:首先根据其调用语句用指针这一事实,我们就知道了其调用的版本应该是该指针的动态类型的函数版本,即Circle::draw,这个问题不大.下面我们来看它的传值参数,前面我们提到缺省参数值是静态绑定的,而pc的静态类型是Shape*,所以该参数的传入值是Shape的该函数版本的缺省值.明白了吧!呵呵!
     晕死,原来是这样,那这时候你是不是禁不住问:为什么C++坚持以这种乖张的方式来运作呢?答案在于运行期效率,如果缺省值也是动态绑定的,那么编译期就必须要有办法在运行期为virtual函数决定适当的参数缺省值.如果这样做的话,就要比目前实现的"在编译期决定"的机制更慢而且更复杂,考虑到执行速度和实现上的简易性,C++放弃了这样的做法.
     好,现在,你为了遵循本款约定却同时提供缺省参数值给你的基类和父类,代码就这样了:
     class Shape{     public:              enum Color{RED,GREEN,BLUE};              virtual void draw(Color color = RED)const = 0;              ...     };     class Circle:public Shape{     public:              virtual void draw(Color color = RED)const {...}     };


     明显的是代码重复嘛!何况你要是想改变缺省值的话,必须要同时改变基类和子类函数的缺省值,一不小心,就会出现漏改或写错的情况,导致意想不到的错误出现.有没用一种更方便的写法呢?当然,你还记得NVI手法吗?额..,(non-virtual interface),要是忘记的话,回过头看看条款35,用这种手法的话,我们写下代码如下:
     class Shape{     public:              enum Color{RED,GREEN,BLUE};              void draw(Color color = RED) const{                     ...                     doDraw(color);                     ...              }              ...     private:             virtual void doDraw(Color color) const = 0;       };     class Circle:public Shape{                   ...     private:              virtual void doDraw(Color color){ ... }     };


     由于draw是non-virtual而non-virtual绝对不会被重新改写(条款36),所以color的缺省值总是为RED.
     请记住:
     ■ 绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数-你唯一应该覆写的东西-却是动态绑定.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4407616>
 

三十八、用复合塑膜出has-a和实现关系

条款38:通过复合塑膜出has-a或"根据某物实现"

内容:
      所谓的复合就是类型之间的一种关系,它的基本的表现形式在我们平时的编写代码过程当中常常出现,比如你准备设计一个类的时候,你写出来的类的对象不是包含了其它的对象?呵呵,对了,这就叫复合.举个例子:

      class Address{...};      class PhoneNumber{...};      class Person{          ...      private:          std::string name_;          Address address_;          PhoneNumber voiceNumber_;          PhoneNumber faxNumber_;      };


      本例中Person就包含了string,Address,PhoneNum合成成分物,它们之间就构成了复合的关系,是不是很简单?呵呵!在前面的条款32中我们提到public继承体系下的子类与父类之间带有"is-a"的意义,而在这里,复合也有自己的意义,它有两个实际的意义,复合意味着"has-a"(有一个)或者is-implemented-in-term-of(根据某物实现),前者的对象属于应用域部分,相当于你塑造的世界中的某些事物,例如人,汽车等.后者的对象则是实现细节人工产品(这产品现实世界中是没有的),像什么mutex,list,container等等,这些对象是你的软件的实现领域.这两个意义理解起来并不是很困难,但在实际的编写代码中如何区分这两种对象关系却并不是你想的那么容易.
      现在的你希望有一个template class去表现一组不重复的对象组成的set(集合),当然你首先想到的就是STL中的set容器,我们都知道STL中set容器是通过balanced search trees实现而成的,它在查找、安插、移除元素时保证较好的速度效率,但却占用了足够客观的空间,如果你的程序的空间比速度要重要,那么标准库的set就不是我们想要的东西了,那怎么办?很简单嘛,你自己写一个不就得了嘛,呵呵.
      考虑到代码的复用,我就想尽量能用现成的代码来模拟它,要是自己亲自每一步都来写,得花我不少时间呢?呵呵,没办法,我这人就是比较懒,想来想去,我决定底层用linked list来实现,因为标准库中有list容器!可以把set对象看成list对象,于是我就这样声明了set template:
      template<typename T>      class Set:public std::list<T>{...}


      看起来似乎不错,不过稍微细想了一下,觉得有些地方不得劲:如果Set是list子类的话,那么list里面的每一件事情对Set来说应该都适用,但list可以含重复元素Set却不可以,晕死!所以说将这两个类强扭成父子关系并不是很合适,其实在这里你的Set对象可以由一个list塑膜出来,你可以这样做:
      template <class T>      class Set{      public:             bool member(const T& item)const{                     return std::find( list_.begin(), list_.end(), item ) != list_.end() );             }             void insert(const T& item){                    if( !member(item) )                        list_.push_back(item);             }             void remove(const T& item){                    typename std::list<T>::iterator iter = std::find( list_.begin(),                                                                                               list_.end(),                                                                                               item );                    if( iter != list_.end() )                        list_.erase(iter);             }             size_t size()const{                    return list_.size();             }      private:             std::list<T> list_;      };


      今天的内容比较少,也比较简单,但是在平时的编写代码的时候我们却经常遇到,所以大家要注意.
      请记住:
      ■ 复合的意义和public继承完全不同.
      ■ 在应用域,复合意味着has-a(有一个).在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出).


来源: <http://blog.csdn.net/scofieldzhu/article/details/4420069>
 

三十九、审慎使用private继承

条款39:明智而审慎地使用private继承
  Use private inheritance judiciously.
内容:
     前面的条款中我们谈到将public继承视为一种is-a关系,在这里你不免有产生一个疑问:那对于private继承,我们应该视为什么关系呢?我们来看下面这个测试:

     class Person{...};     class Student:private Person{...};     void eat(const Person& p);     void study(const Student& s);     Person p;      //p is a person     Student s;     // s is a student.     eat(p);        //ok,p is a person,can eat something     eat(s);        //damned! it's surprise !  isn't student person?????


     从上面的测试我们可以看出来,对于private继承,我们不能仅仅地将它视为is-a关系,那这种继承关系到底我们如何看待它,在软件设计的层面上有什么意义?带着这些问题,我们继续往下看.
     private继承其实意味着implemented-in-terms-of(根据某物实现出).如果你写的class Derived以private继承自class Base,你的用意是为了让Dervived类采用到Base类中的某些特性,而不是因Dervied对象与Base对象存在一些观念上的关系.其实到这里我们应该看的出来:private继承纯粹只是一种实现的技术而已.这种继承关系在软件"设计"层面上没用意义,其意义只在于其实现层面.
     既然private继承意味着is-implemented-in-term-of(根据某物来实现),而前面一款我们也提到了复合(composition)的意义也是如此,那在实际的运用当中,这两者之间做如何取舍呢?我们的答案是这样的:尽可能使用复合,必要时才使用private继承.必要的时候??什么时候才是你说的"必要的时候"?主要就是当protected成员和/或virtual函数牵扯进来的时候.
     为了让你更加理解上面我们所说的,我们来看下面这个具体的例子.这个例子涉及到Widget类,我们现在不仅想知道该类的成员函数多么频繁地被调用,而且我们也想了解一段时间以后的调用比例的变化.于是我们决定记录Widget每个成员函数的被调用次数.为了实现这项功能,我们需要设定某种定时器.为了避免写新代码,我在我的百宝箱里翻箱倒柜地捣鼓了半天,终于开心地发现了这个Timer:
     class Timer{     public:         explicit Timer(int tickFrequency);         virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次.         ...     };


     this is what we want!一个Timer对象,我们可以调整任何频率滴答前进,每次滴答就调用我们重新定义的virtual函数,让其取出Widget的当时状态.That's perfect!为了让Widget重新定义virtual函数,将Widget继承自Timer貌似是一个不错的设计,要是将它们之间的继承关系定性为public继承,显然是不合适的(public继承是一种is-a的关系).这里我们必须用private继承:
     class Widget:private Timer{     private:         virtual void onTick()const; //查看Widget状态...         ...     };


     这样的设计是很好,但是并非绝对必要,我们用复合照样能实现,看下面的另一种设计:
     class Widget{     private:          class WidgetTimer:public Timer{          public:                 virtual void onTick()const;                 ...          };          WidgetTimer timer_;          ...     };


     这样的设计比上一个好像复杂了点,但是它多了上一个设计没用的优点:
     第一,你可能想让Widget拥有derived class,但同时呢,你又不想让derived class重新定义onTick函数,显然如果Widget继承自Timer,这个想法就不能实现,即使是private继承也不可能(条款35中提到derived可以重新定义virtual函数,即使不得调用它). 当若用复合实现的话,内部的private成员继承Timer,Widget的derived classes将无法取用WidgetTimer,因此无法继承它或重新定义virtual函数.
     第二,你可能会想将Widget的编译依赖性降至最低.如果Widget继承Timer,当Widget被编译时Timer的定义必须可见,所以定义Widget的那个文件恐怕必须#include Timer.h,如果WidgetTimer之外而Widget内含一个指向WidgetTimer,Widget就可以只带一个WidgetTimer的声明式,不再需要include任何与Timer有关的东西.
     既然复合的优点比private继承多出这么多的优点,那么我们是不是应该舍弃private继承而全部用复合来代替呢?答案是否定的,我们来看一下激进的情况,下面有一个空类empty与HoldsAnInt类:
     class Empty{};//啥都没用     class HoldsAnInt{     private:         int x_;         Empty empty_;     };


     你会发现sizeof(HoldsAnInt) > sizeof(int),原因很简单Empty类,并不是"空"类,其实在大多数编译器中,sizeof(Empty)获得1.通常是C++官方规定默默安插一个char到空对象内.然而aligment可能造成编译器为类似HoldsAnInt这样的class加上一些衬垫,故可能HoldsAnInt对象不仅获得一个char大小,也许实际上被放大到足够存放一个int.那我再用private继承来看看:
     class HoldsAnInt:private Empty{     private:          int x_;     };


     得到的结果 sizeof(HoldsAnInt) == sizeof(int).这就是所谓的空白最优化-EBO(empty base optimization),我所试过的所有编译器都有这样的结果.如果你的客户很在意空间,那么你就得注意一下EBO.注意EBO只在单一继承(非多重继承)下才可行.EBO无法被施行于"拥有多个base"的dervied classes身上.
     大多数classes并非empty.所以EBO很少成为private继承的正当理由.大多数继承相当于is-a,这里指public继承而不是private继承.复合和private继承都意味着is-implemented-in-terms-of,但复合比较容易理解,所以无论是么时候,只要可以,你还是应该选择复合.当你面对"并不存在is-a关系"的两个classes,其中一个需要访问另一个protected成员,或需要重新定义一个或多个virtual函数,private继承极有可能成为正统设计策略.
     请记住:
     ■ Private继承意味着is-implementd-in-terms-of(根据某物实现出).它通常比复合(composition)的级别低.当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时候,这么设计是合理的.
     ■ 和复合(compoistion)不同,private继承可以造成empty base最优化.这对置于"对象尺寸最小化"的程序库开发者而言,可能很重要.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4465531>
 

四十、审慎使用多重继承

条款40:明智而谨慎地使用多重继承
 Use multiple inheritance judiciously.

内容:
     当我们提到多重继承(multiple inheritance:MI)时,就不得不面对当前C++的两个基本阵营:一个认为单一继承SI(single inheritance) 是好的,MI一定更好!令一个阵营认为,SI是好的,但MI不值得我们去频繁使用.本款我们就带领大家来了解一下关于MI的两个基本观点.
     先看下面这个例子:

    class BorrowableItem{ //图书馆允许你借出的东西     public:      void checkOut();  //离开时进行检查      ...     };     class ElectronicGadget{ //电子机电     private:      bool checkOut()const;//执行监测,返回监测结果      ...     };      //MP3Player同时继承BorrowableItem与ElectronicGadget,(图书馆可以借出MP3 player)     class MP3Player:public BorrowableItem,public ElectronicGadget{ ...};     //下面我们来测试上面类的功能,看是否完整     MP3Player player;     player.checkOut();    //哒哒哒哒,问题来了:你说哪个checkOut将被调用?</span>


     注意这里对checkOut的调用产生了歧义.你现在一定说,这怎么可能?BorrowableItem::checkOut为public, ElectronicGadget::checkOut为private,测试代码中直接调用的版本肯定只有BorrowableItem::checkOut嘛!这种正常的思考模式看起来还不错.不过我的解释是,函数调用是否产生歧义取决于C++对重载函数的调用规则的解析,这里的规则是:C++总是在找出调用的最佳匹配函数之后,才开始对该函数"是否可调用"进行判断!对于这个例子,就是说,本例子中的两个checkOut都有着相同的匹配程度,所以编译器不能确定所谓的最佳匹配,于是抛出"调用产生歧义"的错误.做完了这一步之后就结束检查,当然不会对函数的可调用性进行审查.呵呵!
     当然你可以明确的指出调用哪一个base class内的函数来解决这个歧义:
     player.BorrowableItem::checkOut();
     你也可以尝试调用另外一个版本的checkOut:
     player.ElectronicGadget::checkOut();
     显然你会获得一个"尝试调用private成员函数"的错误. 
     当多重继承中的两个或多个子类同时继承相同的基类的时候,如果用普通的public继承体系的话,就出现最顶层基类数据被重复复制的问题,于是就出现了virtual base继承来解决这个问题,我想大家都很熟悉它的便利了,在这个时候就有了一个简单的规则:在多重继承体系中,如果你使用public继承,请改用virtual public继承.从正确性来看,这个规则很不错.但正确性并不是唯一的观点.你必须考虑一下后果:使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们的体积还要大.访问virtual base class的成员变量时,比访问non-virtual base class的成员变量速度慢.很明显:你得为virtual继承付出代价.
     virtual继承的成本还不止上面提到的,具体的你可以自己仔细的思考一下,在这里我们给出了一些忠告:(1)非必要不要使用virtual base,平常请使用non-virtual继承.(2)如果你必须使用virtual base class,尽可能避免在其中放置数据.
     下面我们来看看下面这个IPerson接口类:
     class IPerson{     public:      virtual ~IPerson();      virtual std::string name() const = 0;      virtual std::string birthDate() const = 0;     };


     我们要想得到IPerson接口,就必须实例化该类,条款31中我们用factory function去创建"派生自IPerson的对象":
     //根据数据库id创建一个Person对象     std::tr1::shared_ptr<IPerson> makePerson( DatabaseID personIdentifier );     //取得一个数据库id     DatabaseID askUserForDatabaseID();     //执行代码     DatabaseID id( askUserForDatabaseID() );     std::tr1::shared_ptr<IPerson> person( makePerson(id) );     ...


     我们知道,makePerson内部肯定创建了一个派生自IPerson的具体类,我们暂且将它称为CPerson,该类必须独自实现接口中的方法.但在这里我们已经有了一个PersonInfo类,该类提供了CPerson所需要的东西: 
 
    class PersonInfo{     public:      using std::string;      explicit PersonInfo(DatabaseID personID);      virtual ~PersonInfo();      virtual const string theName()const;      virtual const string theBirthDate()const;      ...     private:      //返回左界限符      virtual const string valueDelimOpen()const;      //返回右界限符      virtual const string valueDelimClose()const;     };


     以代码实现起来可能像这样:
     const string PersonInfo::valueDelimOpen()const{         return "["; //'['并不是每个人都喜欢的,子类可以重新改写实现     }     const string PersonInfo::valueDelimClose()const{         return "]";//']'并不是每个人都喜欢的,子类可以重新改写实现     }     const string PersonInfo::theName()const{         string value = valueDelimOpen(); //先加入左界限符         .....//现在将对象的name名字添加到value后面         value += valueDelimClose(); //加入右界限符         return value;     }


     CPerson和PersonInfo的关系是,PersonInfo刚好可以帮助实现CPerson,因此它们的关系就构成了is-implemented-in-terms-of(根据某物实现出),而我们知道这种关系可以两种技术实现,复合和private继承.前一条款指出复合比较受到欢迎,但如果需要重新定义virtual函数,那么继承是必要的.本例中CPerson需要重新定义valueDelimOpen与valueDelimClose,所以我们直接用private继承.
     class CPerson:public IPerson,private PersonInfo{     public:      explicit CPerson(DatabaseID personID):PersonInfo(personID){}      virtual std::string name()const{          return PersonInfo::theName();      }      virtual std::string birthDate()const{          return PersonInfo::theBirthDate();      }     private:         const std::string valueDelimOpen()const{return "";}         const std::string valueDelimClose()const{return "";}     };


     好了,这个设计还不错吧.呵呵,其实这个例子是告诉你,多重继承也有它的合理用途.
     请记住:
     ■ 多重继承比单一继承复杂.它可能导致新的歧义性,以及对virtual继承的需要.
     ■ virtual继承会增加大小、速度、初始化(赋值)复杂度等到成本.如果virtual base classes不带任何数据,将是最具实用价值的情况.
     ■ 多重继承的确有正当用途.其中一个情节涉及"public继承某个Interface class"和"private继承某个协助实现的class"的两点相组合.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4469292>

(七)、模板与泛型编程

四十一、隐式接口与编译多态

条款41:了解隐式接口和编译期多态
(understand implicit interfaces and compile-time polymorphism.)

内容:
     今天遇到这样一个处理Widget的函数doProcessing,其实现如下:

     void doProcessing(Widget& w)     {          if( w.size() > 10 && w != someNastyWidget ){               Widget temp( w );               temp.normalize();               temp.swap( w );          }     }


     看完上面的代码以后,我们可以立即得到下面两个简单的结论:
     (1) 由于w是一个Widget类对象,故其必须支持Widget的接口,我们可以在Widget定义的源码中找到这样的接口(一般在Widget的头文件中声明接口),如此的接口我们通常称之为显式接口(explicit interface),也就是源码中明确可见的接口.为了方便一下描述,我们假设其类的简略声明如下:
    class Widget{     public:         Widget();         virtual ~Widget();         virtual std::size_t size()const;         virtual void normalize();         void swap(Widget& other);         ...     };


     (2) 由于Widget的某些成员函数是virtual函数,w的引用对象对那些函数的调用将表现出运行期多态(runtime polymorphism),也就是说于运行期根据w的动态类型决定调用哪一个函数.
     当我们在面向对象编程世界中畅游时,对于这种类的显式接口声明与运行期多态性的调用,我们已经很习以为常了.然而Template及泛型编程的时代的来临降低了显式接口与运行期多态的重要性.替换它们的是隐式(implicit intefaces)和编译期多态(comile-time polymorphism),想了解它们是什么吗?
     那我们先将doProcessing从函数转变成函数模板,看此过程中发生了哪些变化:
 
    template<typename T>     void doProcessing(T& w)     {          if( w.size() > 10 && w != someNastyWidget ){                T temp( w );                temp.normalize();                temp.swap( w );          }     }


     那我们重新审视一下这个接口,我们可以得出一些结论:
     (1) w必须支持哪一种接口,系由template中执行w身上的操作来决定.本例看来w的类型T好像必须支持size,normalize和swap成员函数copy构造函数(用以建立temp)、不等比较(inequality comparision,用来比较someNastyWidget).我们很快就会看到这并非完全正确,当对目前而言足够真实.重要的是,这一组表达式(对此template而言必须有效编译)便是T必须支持的一组隐式接口(implicit interface).
     (2) 凡涉及到w的任何调用,例如operator>和operator!=,有可能造成template具现化(instantiated),使这些调用得以成功.这样的具现行为发生在编译期."以不同的template参数具现化function templates"会导致调用不同的函数,这些便是所谓的编译期多态(compile-time polymorphism).
     通常显式接口由函数签名式(也就是函数名称、参数类型、返回类型)构成,而隐式接口就完全不同了.它并不是基于函数签名式,而是由有效表达式(valid expression)组成.加诸于template参数身上的隐式接口,就像加诸于class对象身上的显式接口一样真实,而且两者都在编译器完成检查.就像你无法以一种"与class提供之显式接口矛盾"的方式来使用对象(代码将通不过编译),你无法在template中使用"不支持template所要求之隐式接口"的对象(代码一样编译不过).
     请记住:
     ■ class和templates都支持接口(interfaces)和多态(polymorphism).
     ■ 对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtual函数发生于运行期.
     ■ 对template参数而言,接口是隐式的(implicit),奠基于有效表达式.多态则是通过template具现化函数重载解析(function overloading resolution)发生于编译期.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4479019>
 

四十二、typename双重含义

条款42:了解typename的双重意义
 Understand the two meanings of typename.

内容:
     看下面template声明式中,class与typename有什么不同?

     template<class T> class Widget;//use "class "      template<typename T> class Widget;//use "typename"
     某些程序员喜欢用class,因为可以少打几个字母.还有一部分开发人员喜欢在接受任何类型时使用typename,而在接受用户自定义类型时保留旧式的class.然而实际上,从C++角度来看,声明template参数时,不论使用关键字class或typename意义完全相同.但这并不等于说C++总是 把class和typename视为等价.有时候你一定得使用typename.
     看下面这个无聊的template函数,以无聊的方式实现,甚至连编译都通不过,但我们先忽略这些事实,我们来看它的实现体:
     tempalte <typename C>     void print2nd(const C& container)//打印容器内的第二元素     {         if( containter.size() >= 2 ){             C::const_iterator iter( containter.begin() );             ++iter;             int value = *iter;             std::cout << value;         }     }

     对于这里的C::const_iterator而言,我们现在无法知道其是否为一个类型,除非你告诉我C是什么.那编译器开始解析template print2nd时,它将如何解析它呢?C++的解析此歧义状态的规则是:如果在template中遭遇一个嵌套从属名称,它遍假设这名称不是个类型,除非你告诉它是.所以在缺省情况下嵌套从属名称不是类型.如果你告诉它你是类型的话,你不得不在其前面加上typename进行修饰.即:
     template <typename C>     void print2nd(const C& containter)     {         if( container.size() >= 2 ){             typename C::const_iterator iter( container.begin() );             ...         }     }


     一般规则很简单:任何时候你想要在template中指定一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字typename.好,下面我们来看一个例外,这一规则的例外是,typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initailization list(成员初值列)中作为base class修饰符.例如:
     template <typename T>     class Derived:public Base<T>::Nested{ //base class list中不允许出现"typename"     public:         explicit Dervied(int x)          : Base<T>::Nested(x){ //成员初始化列表中不允许"typename"              typename Base<T>::Nested temp; //既不在base class list也不在初始化列表中,作为一个base class修饰符需加上typename.             ...         }         ...     };
     在本款快结束的时候,我们要注意这样的一个问题:typename相关规则在不同的编译器上有不同的实践.某些编译器接受的代码原本该有typename却遗漏了;原本不该有typename却出现了;还有少数编译器(通常是较旧版本)根本就拒绝typename.这意味typename和"嵌套从属类型名称"之间的互动,也许会在移植性方面带给你某种温和的头疼(==!作者你在搞什么鬼).
     请记住:
     ■ 声明template参数时,前缀关键字class与typename可互换.
     ■ 请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)或member initailization list(成员初值表列)内以作为base class修饰符.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4482164>
 

四十三、处理模板化基类名称

条款43:学习处理模板化基类内的名称
(Know how to access names in templatized base classes.)

内容:
     现在我们接到一个编码任务,任务要求我们的目标程序能够传送信息到不同的公司去.这里的信息可以分为:被译成密码的信息和未经加工信息明文信息.我们分析了任务以后认为,在目标程序的编译期间我们就可以决定哪一个信息传送至哪一家公司.所以我们采用了template来实现,大概的伪代码实现如下:

     class CompanyA{     public:         ...         void sendClearText(const std::string& msg);         void sendEncrypted(const std::string& msg);         ...     };     class CompanyB{     public:         ...         void sendClearText(const std::string& msg);         void sendEncrypted(const std::string& msg);         ...     };     ... //其它公司的classes.     class MsgInfo{...};//这个class为将来生产保存信息     template<typename Company>     class MsgSender{     public:         ...         void sendClear(const MsgInfo& info){             std::string msg;             ...//根据info产生信息             Company c;             c.sendClearText(msg);         }         void sendSecret(const MsgInfo& info){...} //这里调用的是c.sendEncrypted.     };


     上面的设计属于一个良好的设计.现在我们又需要另外一种MsgSender,它的特殊之处在于,在每次发送信息时,它能够log某些信息.显然将这个类视为MsgSender的一个子类将是一个不错的选择:
     template <typename Company>     class LoggingMsgSender:public MsgSender<Comany>{     public:         ...         void sendClearMsg(const MsgInfo& info){ //为避免"名称遮掩"现象的发生,采用了一个不同的名称             ...// record status information before sending message             sendClear(info);             ...//record status information after sending message.         }     ...        };


     这样的代码看起来没用什么可以指责的地方,但编译器却不让其通过编译.此时的编译器抛出了"sendClear不存在"的抱怨.可sendClear明显就在base class内部啊?郁闷,这是为什么倪?别急,我来解释一下:当编译器遇到LoggingMsgSender定义式时,由于Company是一个template参数,不到LoggingMsgSender被具现化的时候,编译器是无法知道Company是什么的.由于无法知道Company是什么,那么它就无法确定MsgSender是什么,也就无法获知其否有个sendClear函数. 停一下,你可能现在对我的解释会有一个问题:为什么根据template Company就不能确定Company是什么了呢?template Company不是有声明式嘛!在这里我提出:我们不能根据一般性模板类的声明式来确定具现化类具有的操作,因为模板类有个特化声明版本的问题.为了让问题具体化,假设现在有另外一个CompanyZ坚持使用加密通讯:
     class CompanyZ{ //这个class 不提供sendClearText函数     public:         ...         void sendEncrypted(const std::string& msg);             ...     };

     现在的问题是:一般性的模板类MsgSender声明对CompanyZ并不适用,因为那个template提供了一个sendClear函数(内部调用sendClearText函数),而这对CompanyZ对象不合理.欲矫正这个问题,我们可以针对CompanyZ产生一个MsgSender特化版:
     template<>     class MsgSender<CompanyZ>{     public:         ...         void sendSecret(const MsgInfo& info){...} //只提供sendSecret方法         ...     };


     好了,我们再次来看LoggingMsgSender::sendClearMsg:
     tempalte <typename Company>     void LoggingMsgSend<Company>::(const MsgInfo& info){         ...//         sendClear(info); //如果这里Company == CompanyZ,这个函数就不存在         ...//     }

     哒哒哒哒,问题来了吧!这就是C++拒绝这个调用的原因:它知道base class template有可能被特化,而那个特化版本可能不提供和一般性template相同的接口.就某种意义而已,当我们从Object Oriented C++跨进Template C++,继承就不像以前那么畅行无阻了.
     麻烦点就麻烦点,问题终归还是要解决的.我们有以下三个方法来解决此类问题:
     方法一:在base class函数调用动作之前加上"this->":
     template <typename Company>     void LoggingMsgSender<Company>::sendClearMsg(const MsgInfo& info){         ...         this->sendClear(info); //ok         ...     }

     方法二:使用using声明式.
     
     template <typename Company>     class LoggingMsgSender:public MsgSender<Company>{     public:         //这里的情况不是base class名称被derived class名称遮掩,而是编译器不进入base base         //作用域查找,于是我们通过using声明式告诉它,请它这么做         using MsgSender<Company>::sendClear;//告诉编译器,请它假设sendClear位于base class内         ...         void sendClearMsg(const MsgInfo& info){             ...             sendClear(info);//ok             ...         }     };


     方法三:明明白白指出被调用函数位于base class内:
     template <typename Company>     class LoggingMsgSender:public MsgSender<Company>{     public:         ...         void sendClearMsg(const MsgInfo& info){             ...             MsgSender<Company>::sendClear(info); //ok             ...         }         ...     };


     不过此方法有一个很明显的暇疵:如果被调用的是virtual函数,上述的明确资格修饰会关闭"virtual绑定行为".
     根本而言,本条款探讨的是,面对"指涉base class members"之无效references,编译器的诊断时间可能发生在早期(当解析derived class template的定义式时),也可能发生在晚期(当那些template被特定之template实参具现化时).C++的政策是宁愿较早诊断,这就是为什么"当base classes从template 中被具现化时"它假设它对那些base classes的内容毫无所悉的缘故.
     请记住:
     ■ 可在derived class templates内通过"this->"指涉base class templates内的成员名称,或由一个明白写出"base class资格修饰符"完成.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4499637>
 

四十四、参数无关代码抽离模板

条款44:将与参数无关的代码抽离templates
(Factor parameter-independent code out of templates.)

内容:
     自从我们进入模板编程的第一天,我们就意识到template不仅大大地减少了代码的执行时间,而且能够避免代码潜在的重复.我们不在需要为20个相似的classes而每一个都带有15个成员函数编写独立的一份代码去分别实现,我们只需要键入一个class template,留给编译器去具现化那20个你需要的相关classes和300个函数.如此神奇的技术是不是令你感到很兴奋.是的,这个对每一个软件开发人员来说无疑是一个重大的编码优化手段,然而有时候,如果你不小心,使用templates可能会导致代码膨胀:其二进制码带着重复(或几乎重复)的代码、数据,或两者.
     在你编码的时候,通常你有一个强大的工具.那就是:共性与变性分析.其概念从字面上我们就可以了解,即使你从未写过一个template,你始终在进行着这样的分析.当你在写两个函数的时候,你发现它们之间有着相同的部分,此时的你惯性般地将他们之间"公共部分"提出来,作为第三个函数,然后让前两个函数都调用这个第三个函数;同样的道理,当你编写某个class时候,你发现其某些部分与另外一个class的某些部分相同,你也不会去重复该相同部分,而是把相同部分搬移到新的class去,然后在使用复合或继承,令原classes取用这些共同特性.而原classes的特异部分则保持不动. 好,我们将这种分析方法推而广之,它对template同样适用.但在template代码中,重复是隐晦的:毕竟只存在一份template源码,所以你编码时必须万分小心去防止template具现化多次时可能造成的重复.
     举个例子,假设现在你要为固定尺寸的矩阵编写一个template类,该类声明要支持矩阵的逆运算.你开始了你的代码:

     template <typename T, std::size_t n> //矩阵元素类型T,尺寸大小为n     class SquareMatrix{     public:         ...         void invert(); //逆运算     };


     写出这样代码很自然,接下来我们考虑如何使用这个template:
     SquareMatrix<double,5> square1;     ...     square1.invert(); //调用 SquareMatrix<double,5>::invert     SquareMatrix<double,10> square2;     ...     square2.invert(); //调用 SquareMatrix<double,10>::invert</span>


     这会发生什么事情?这些函数并非完完全全相同.但除了常量5和10,这两个函数的其它部分完全相同.这就是template引出代码膨胀的一个典型例子.
     如果你的代码中遇到这样的情况,你会怎么去"亡羊补牢".你的本能会让你为它们建立一个带数值参数的函数,然后用5和10来调用这个带参函数,这样就不会导致代码重复了.好,想法不错,我们来实现你的idea.
     template <typename T>     class SquareMatrixBase{     protected:         ...         void invert(std::size_t matrixSize);//以尺寸大小作为参数的求逆矩阵函数         ...     };     template <typename T,std::size_t n>     class SquareMatrix:private SquareMatrixBase<T>{     private:         using SquareMatrixBase<T>::invert; //避免遮掩base版本的invert     public:         ...         void invert(){ this->invert(n); }     };


     这里请注意SquareMatrix和SquareMatrixBase之间的继承关系是private.这反应一个事实:这里的base class只是为了帮助derived class实现,而不是为了表现SquareMatrix与SquareMatrixBase之间是一个is-a关系(关于private继承,见条款39).
     到目前为止,情况还令人满意.不过,现在有一个值得我们去思考的问题出现了:SquareMatrixBase::invert如何知道该操作什么数据?虽然它知道了矩阵的尺寸,但怎么能够知道那个矩阵的数据放在什么地方了倪?这里的一个可行的方法是令SquareMatrixBase储存一个指针,该指针指向矩阵数值所在的内存.那我们要操作的东西就有了.哈哈,that's perfect!成果貌似是这样滴:
     template <typename T>     class SquareMatrixBase{     protected:         SquareMatrixBase(std::size_t n, T* memory)         :size_(n),data_(memory){}           void setData(T* data){ data_ = data; }         ...     private:         std::size_t size_; //矩阵大小         T* data_;          //矩阵数据     };     template <typename T, std::size_t n>     class SquareMatrix:private SquareMatrixBase<T>{     public:         SquareMatrix():SquareMatrixBase<T>(n,data){} //将矩阵大小和数据指针给base class.         ...     private:         T data_[n*n];     };


     以上实现当中,可能造成对象自身非常大.于是我们可以将矩阵数据放进heap中:
     template <typename T,std::size_t n>     class SquareMatrix:private SquareMatrixBase<T>{     public:         SquareMatrix():SquareMatrixBase<T>(n,0),data_(new T[n*n]){              this->setData(data_.get()); //使得base class获得矩阵数据         }         ...     private:         boost::scoped_array[T] data_;     };


     喔喔,这样的解决方案是不是很棒?是的,很棒,但必须付出代价.带有尺寸模板参数的那个invert版本,可能生成比共享版本(就是以函数参数传递尺寸或存储在对象内)更佳的代码.比如在尺寸专属版本中,尺寸是个编译期常量,因此可以籍由常量的广传达到最优化,包括把它们折进被生成指令中成为直接操作数.这在"与尺寸无关"的版本中是无法办到的.而另一个方面,不同大小的矩阵只拥有单一版本的invert,可减少执行文件大小,也就因此降低程序进程所使用的那一组内存页大小,并强化指令高速缓存区内的引用集中化.这些都可能使得程序执行的更加快速,超越"尺寸专属版"invert的最优化效果. 那哪一个影响占主要地位?要想知道这个答案,唯有让两者尝试并观察你的平台行为以及面对代表性数据组时的行为.
     另一个效能评比所关心的主题就是对象大小.这里我不再详细表述了,大家自己可以分析一下.
     本条款只讨论由non-type template parameter(非类型模板参数)带来的膨胀,其实type parameter(类型参数)会导致膨胀.具体解释请看原书作者的表述,限于篇幅的问题,在这里我就不累赘表述了.

其实type parameter(类型参数)也会导致膨胀。例如许多平台上int和long有相同二进制表述,所以像vector<int>和vector<long>的成员函数有可能完全相同——这正是膨胀的最佳意义。某些链接器会合并完全相同的函数实现码,但有些不会,后者意味着某些templates被具现化为int和long两个版本,并因此造成代码膨胀。类似情况,大多数平台下,所有指针类型都有相同的二进制表述,因此凡是templates持有指针者(例如list<int*>,list<const int*>,list<SquareMatrix<long,3>*>等等)往往应该对每一个成员函数使用唯一一份底层实现。这很具代表性的意味,如果你实现某些成员函数而他们操作强型指针(即 T*),你应该令他们调用另一个操作无类型指针(即 void*)的函数,而后者完成实际工作。某些c++标准程序库实现版本的确为vector,deque,list等templates做了这件事。如果你关心你的templates可能出现代码膨胀,也许你会想让你的templates也做相同的事情。

来源: <http://www.cnblogs.com/lidan/archive/2012/02/15/2353160.html>


  请记住:

     ■ Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系.
     ■ 因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数
     ■ 因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码.
来源: <http://blog.csdn.net/scofieldzhu/article/details/4502514>

四十五、运用成员函数模板接受兼容类型

条款45:运用成员函数模板接受所有兼容类型
(Use member function templates to accept "all compatible types.")

内容:
    不知道大家注意到没有,在类的继承体系中,对象指针一直在为我们做一件很好的事情:支持隐式转换(implicit conversions).Derived class指针可以隐式转换为base class指针,"指向non-const对象"的指针可以转换为"指向const对象"......等等.下面例子展示了继承体系之间的可能进行的转换操作:

    class Top{ ... };    class Middle:public Top{...};    class Bottom:public Middle{...};    Top* top1 = new Middle;        //将Middle*转换为Top*    Top* top2 = new Bottom;        //将Bottom*转换为Top*    const Top* const_top2 = top1;   //将Top*转换为const Top*


    在前面的条款13中我们提到,像std::auto_ptr和tr1::shared_ptr这样的智能指针,能够提供原始指针没有的机能.比如能够在正确的时机自动删除heap-based资源.本款中我们自然就想到了,如果智能指针也能支持上述的隐式操作转换,那岂不是很方便.于是我们在这里试图让类似下面的代码通过编译:
    template <typename T>    class SmartPtr{    public:        explicit SmartPtr(T* realPtr);//智能指针通常以原始指针完成初始化        ...    };    SmartPtr<Top> top1_smart_ptr = SmartPtr<Middle>(new Middle);    SmartPtr<Top> top2_smart_ptr = SmartPtr<Bottom>(new Bottom);    SmartPtr<const Top> const_top2_ptr = top1_smart_ptr;


    但是,现实的情况好像并不是像我们想象的那么美好:同一个template的不同具现体(instantiation)之间并不存在故有的联系.也就是说,编译器视SmartPtr<Middle>和SmartPtr<Top>为完全不同的classes.它们之间没有任何必然的联系.这个问题不小啊,但一想到如果SmartPtr classes之间具有了这样的转换能力,世界将会变的更加的美好.我就增强了要实现这种模拟的信心.好,接着往下看.
    在上述继承体系中,每一条语句都创建一个新式智能指针对象,我们自然就想到了,我们应该把工作的焦点放在如何编写智能指针的构造函数上,使其满足我们的转型需要.而稍微仔细的观察一会儿,我们就有了一个新的顾忌:我们永远无法写出我们需要的构造函数.为什么呢?因为继承基类的子类可以有很多个,每诞生一个新的子类,我们就必须要在基类智能指针中添加一个为实现其向子类转换的新的构造函数.那样的代码不仅理解起来很晦涩,更难以维护.在如此"绝境"之下我们想到了模板成员函数,有了这样的"利器"我们就在战术上从被动为主动了,哈哈.
    template <typename T>    class SmartPtr{    public:        template <typename U>        SmartPtr(const SmartPtr<U>& other); //copy constructor        ...    };


    等一下,构造函数为什么没有explicit修饰?我故意的.因为要完成原始指针之间的隐式转换,我们需要支持这样的操作.如果SmartPtr也像auto_ptr和tr1::shared_ptr一样,提供一个get成员函数去发挥智能指针对象的原始指针副本.上面的代码我们可以写的更清楚一点:
    template <typename T>    class SmartPtr{    public:        template <typename U>        SmartPtr(const SmartPtr<U>& other):held_ptr_( other.get() ){...}         //用other.held_ptr_初始化this->held_ptr_,这里的other的原始对象如果是      //this原始对象的子类的话,这里就完成子类向父类的隐式转换过程.        T* get()const{ return held_ptr_;}        ...    private:        T* held_ptr_; //这是SmartPtr持有的内置指针.            };


    呵呵,不错吧!其实member function template(成员函数模板的效用不限于构造函数),它们常常扮演的另一个角色就是支持赋值操作.这点在TR1的shared_ptr中获得了绝好的发挥.下面是TR1规范中关于tr1::shared_ptr的一份摘录.

    template<class T>    class shared_ptr{    public:        template<class Y>        explicit shared_ptr(Y* p);        template<class Y>        shared_ptr(shared_ptr<Y> const& r);        template<class Y>        explicit shared_ptr(weak_ptr<Y> const& r);        template<class Y>        explicit shared_ptr(auto_ptr<Y>& r); //为什么这里不要const? 因为当你复制一个auto_ptr,它们其实被改动了.        template<class Y>        shared_ptr& operator=(shared_ptr<Y> const& r);        template<class Y>        shared_ptr& operator=(auto_ptr<Y>& r);//为什么这里不要const? 原因同上        ...    };


    这里还有一点我们要注意:member template并不会改变语言规则.而在前面我们曾提到,如果程序需要一个copy构造函数,你却没有声明它,编译器会为你暗自生成一个.在class内申明泛化copy构造函数(member template)并不会阻止编译器生成它们自己的copy构造函数(non-template),故你想要控制构造的方方面面,你必须同时声明泛化copy构造和普通copy构造.赋值操作也是原因.下面的tr1::shared_ptr的定义摘要,就证明了这点是正确的:
    template<class T>    class shared_ptr{    public:        shared_ptr(shared_ptr const& r);   //copy 构造函数        template<class Y>          shared_ptr(shared_ptr<Y> const& r); //泛化的copy构造        shared_ptr& operator=(shared_ptr const& r); //copy assignment        template<class Y>        shared_ptr& operator=(shared_ptr<Y> const& r); //泛化copy assignment        ...    };
    请记住:
    ■ 请使用member function template(成员函数模板)生成"可几乎所有兼容类型"的函数
    ■ 如果你声明member template用于"泛化copy构造"或"泛化assignment操作",你还需要声明正常copy构造函数和copy assignment操作符.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4514196>
 

四十六、类型转换时为模板定义非成员函数

条款46:需要类型转换时请为模板定义非成员函数
    Define non-member functions inside templates when type conversion are desired.
    
    还记得在条款24中,我们提到只有non-member函数才有能力'在所有实参身上实施隐式类型转换'.今天我们讨论的东西与该条款相关,如果现在的你恰好忘记了前面这条款的内容,那么我建议你还是先把那条款在消化一下,再开始这一款吧,呵呵,'磨刀不误砍柴工'嘛!废话我就不多说了,我们进入正题.
    '只有non-member函数才有能力在所有实参身上实施隐式类型转换',在泛型编程的世界里,这是否也成立呢?下面我们来稍微修改一下前面这款例子中的代码,然后验证一下是这句话是否依然成立.

    template<typename T>    class Rational{    public:        Rational(const T& numberator = 0, const T& denominator = 1);        const T numerator()const;        const T denominator()const;        ...    };

    template<typename T>    const Rational<T> operator*(const Rational<T>& left_handle_side,                                const Rational<T>& right_handle_side );


    我们希望此时的它能够一往无前的支持混合式算术运算,我沙沙地写出下面两行代码进行测试:
    Rational<int> one_half( 1, 2 );    Rational<int> result = one_half * 2; //compile error.

    咦?奇怪,难道换成了模板就不行了?事实确实如此!在条款24中编译器能够知道我们在尝试调用哪个函数(就是接受两个Rationals参数的那个operator*啦),但在这里编译器却不知道我们要调用哪个函数,它们试图想出什么函数被名为operator*的template具现化出来.但现在的问题是它们没有足够大的能耐来完成如此的工作.为了完成具现化工作,必须先算出T是什么,于是它们开始了下面的尝试:
    编译器首先看到了operator*的两个参数,它们的类型分别是Rational<int>(one_half类型)和int(2的类型),每个参数分开考虑.以one_half进行推导比较容易,operator*的第一个参数声明为Rational<T>,而传递给函数的第一实参类型是Rational<int>,故T一定int.但到了第二个参数推导的时候,问题就来了.operator*声明的第二个参数为Rational<T>,但实参确实int类型(2).编译器如何推算T?会不会发生像条款24出现的隐式参数转换呢?(编译器适用Rational<int>的non-explicit构造函数将2转换为Rational<int>,进而将T推导为int)。

但事实却是很惨酷的:它们没有那样做.因为在template实参推导过程中从不将隐式类型转换函数纳入考虑.这下麻烦了,那有什么办法能够解决这一问题的呢?别急,其实我们只需要利用一个事实就可以缓和编译器在template实参推导方面受到的挑战,这个事实就是:template class内的friend声明式可以指涉某个特定的non-member函数.class templates并不依赖template实参推导(后者只施行于function templates身上),所以编译器总是能够在class Rational<T>具现化时得知T(机智的小伙伴).因此,我们的问题的解决方案就出来了:令Rational<T> class声明适当的operator*为其friend函数.

    template<typename T>    class Rational{    public:        ... //同上        friend const Rational operator* ( const Rational& left_handle_side,                                          const Rational& right_handle_side );    };    template <typename T>    const Rational<T> operator*(const Rational<T>& left_handle_side,                                const Rational<T>& right_handle_side )    {...}


    现在对operator*的混合式调用就可以编译了,因为当对象one_half被声明为Rational<int>,class Rational<int>就被具现化出来了,而作为过程的一部分,friend函数operator*也就被自动声明出来了,此时后者为一个具现的函数而不是函数模板罗,因此编译器可在调用它时使用隐式转换,于是混合式调用就基本成功了!基本?难道还有未完成的工作?呵呵,当你编译的时候没问题,是吧?你试试链接呢?竟然无法连接!!SHIT!怎么个情况????
    现在我们回头来思考这个问题.混合式代码通过了编译是因为编译器知道我们要调用哪个函数,但那个函数只被声明与Rational内,并没有被定义出来.而我们意图令此class外部的operator* template提供定义式,但是行不通--------如果我们自己声明了一个函数,就有责任定义那个函数.既然我们没有提供定义式,连接器当然找不到它!
    最简单的可行方法就是将operator*函数本体合并至其声明式内:
    template<typename T>    class Rational{    public:        ... //同上        friend const Rational operator*(const Rational& left_handle_side,                                         const Rational& right_handle_side ){            return Rational( left_handle_side.numerator() * right_handle_side.numerator(),                             left_handle_side.denominator() * right_handle_side.denominator() );        }    };


    简单的Build一下,我们就可以看到,对operator*的混合调用现在可以编译连接并执行了.哦也!operator*定义体放到了类的内部成了inline函数,而inline声明会给类带来冲击.为了最小化这种冲击,我们可以令operator*不做任何事,只调用一个定义于class外部的辅助函数,当然,对本条款中的例子而言,这样做没有太大的意义,因为operator*只是一个单行函数,但对于更复杂的函数而言,这样做也许就有价值.本款的例子典型长成这样:
    template<typename T> class Rational;    template<typename T>     const Rational<T> doMultiply(const Rational<T>& left_handle_side,                                 const Rational<T>& right_handle_side);    template<typename T>    class Rational{    public:        ...        friend const Rational<T> operator*(const Rational<T>& left_handle_side,                                           const Rational<T>& right_handle_side){            return doMultiply( left_handle_side, right_handle_side );        }        ...    };


    好了,今天的讨论就到这里!
    请记住:
    ■ 当我们编写一个class template,而它所提供之'于此template相关的'函数支持'所有参数之隐式类型转换'时,请将
那些函数为'class template内部的friend函数'.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4710458>

四十七、使用traits 类表现类型信息

条款47:请使用traits classes表现类型信息
    Use traits classes for information about types.

    今天我们讨论的这款内容涉及到一个STL实现上的一个关键技术traits技术,简单的说就是类的型别判定技术.
    我们知道STL迭代器可分为五类,我再来简单的唠叨一下:input迭代器只能向前移动,一次一步,客户只可读取(不能修改)它们所指的内容,而且只能读取一次;output迭代器情况类似,只是为了输出;以上这两种分别模仿文件的读写指针,分类的代表为istream_iterators和ostream_iterators.它们两个属于能力最小的迭代器分类,只适合一次性操作算法.第三类迭代器为forward迭代器,该种迭代器能够做上述两种类所能做的每一件事情,而且可以读写所指物一次以上.第四类迭代器为bidirectional迭代器,比前一种威力更大,除了可以向前移动还可以向后移动.STL的list,set,multiset,map,multimap的迭代器都是属于这一分类.最后一种分类也是威力最强的迭代器当属random access迭代器,它可以在常量时间内向前或向后迢遥任意距离.vector,deque和string提供的迭代器就属于这一分类.
    对于这五种分类,C++标准库提供专门的类型标记结构对它们进行区分:

  1.     struct input_iterator_tag{};
  2.     struct output_iterator_tag{};
  3.     struct forward_iterator_tag:public input_iterator_tag{};
  4.     struct bidirectional_iterator_tag:public forward_iterator_tag{};
  5.     struct random_access_iterator_tag:public bidirectional_iterator_tag{};
    下面我们来看STL算法里面的函数advance的实现,其作用就是将某个迭代器移动某个距离:
    template <typename IterT, typename DistT>    void advance(IterT& iter, DistT d);


    为了对所有的迭代器分类都进行,我们最希望以下列方式进行实现:
    template <typename IterT, typename DistT>    void advance(IterT& iter, DistT d)    {        if( iter is a random access iterator ){            iter += d; //针对random access迭代器使用迭代器算术运算        } else { //针对其它迭代器分类反复调用++或者--            if( d >= 0 ){                while( d-- )                    ++iter;            } else {                while( d++ )                    --iter;            }        }            }


    当写完上面的伪以后,我们就会立刻将焦点转移到一个点上:必须能够判断iter是否为random access迭代器,也就是说需要知道类型IterT是否为random access迭代器分类.换句话说我们需要取得类型的某些信息.那就是traits让你得以进行的事:它们允许你在编译期间取得某些类型信息.
    Traits并不是C++关键字或一个预先定义好的构件;它们是一种技术,该技术的要求之一是,它对内置类型和用户自定义类型的表现必须一样好.这就意味着'类型内的嵌套信息'这种东西出局了,因为我们无法将信息嵌套于原始指针内.因此类型的traits信息必须位于类型自身之外.标识程序库的做法是把它放进一个template及其一个或多个特化版本中.这样的templates在标准程序库中有若干个,其中针对迭代器者被命名为iterator_traits:
    template<typename IterT>    struct iterator_traits;


    iterator_traits以两个部分实现上述所言.首先它约定每一个用户自定义迭代器类型必须嵌套一个typedef,名为iterator_category,用来确认类型信息.比如deque可随机访问,所以针对deque迭代器而设计的class看起来会是这个样子:
    template<...>    class deque{    public:        class iterator{        public:            typedef random_access_iterator_tag iterator_category;            ...        };        ...    };


    list迭代器可双向行进,所以应该这样:
    template<...>    class list{    public:        class iterator{        public:            typedef bidirectional_iterator_tag iterator_category;            ...        };        ...    };


    对于iterator_traits只要响应iterator class的嵌套式typedef即可:
    template <typename IterT>    struct iterator_traits{        typedef typename IterT::iterator_category iterator_category;        ...    };


    上面的对用户自定义类型行得通,但是仅仅这样的iterator_traits是不够的,你必须还要提供对指针(也是一种迭代器)的支持,因为指针不可能嵌套typedef.于是我们就让iterator_traits的第二部分专门对付指针,它所利用的工具就是模板的偏特化技术.由于指针的行径与random access迭代器类似,所以iterator_traits为指针指定的迭代器类型如下:
    template<typename IterT>    struct iterator_traits<IterT*>{        typedef random_access_iterator_tag iterator_category;        ...    };


    OK!现在你知道如何来实现一个traits class了,我们来大概简述一下如何设计并实现一个traits class:
    ■ 确认若干你希望将来可取得的类型信息.
    ■ 为该信息选一个名称(例如iterator_category)
    ■ 提供一个template一组特化版本,内含你希望支持的类型相关信息.
来源: <http://blog.csdn.net/scofieldzhu/article/details/4713887>

    继续前面讨论来讲述如何使用traits classes来表现类型信息.好,我们现在有了iterator_traits,我们可以可以这样来实现advance函数:

    template<typename IterT, typename DistT>    void advance(IterT& iter, DistT d)    {        if( typeid( typename std::iterator_traits<IterT>::iterator_category ) ==             typeid ( std::random_access_iterator_tag ) )        ...    }


    这看起来还行是吧,其实它却有些潜在的问题.首先将导致编译问题,我将在下一款讨论这一点,我们现在应该关心更根本的问题.IterT类型在编译期间获知,所以iterator_traits<IterT>::iterator_category也可在编译期间确定.但if语句确是在运行期才会核定.为什么将可在编译期完成的事情延期到运行期才做呢?这不仅浪费时间,也造成可执行文件膨胀.我们真正想要的是一个条件式来判断'编译器核定成功'的类型.恰巧C++的重载机制就满足这个需求,哇哈哈!
    由于重载函数是根据传来的实参选择最佳的重载体,所以为了让advance的行为如我们所期望,我们需要产生几个接受不同类型的iterator_category对象作参数的函数,我们将这函数取名为doAdvance:
    //这份实现用于random access迭代器    template<typename IterT,typename DistT>    void doAdvance( IterT& iter, DistT d, std::random_access_iterator_tag)    {        iter += d;    }    //这份实现用于bidirectional迭代器    template<typename IterT,typename DistT>    void doAdvance( IterT& iter, DistT d, std::bidirectional_iterator_tag)    {        if( d >= 0 ){            while( d-- ){                ++iter;            }        } else {            while( d++ ){                --iter;            }                        }    }    //这份实现用于input迭代器    template<typename IterT,typename DistT>    void doAdvance( IterT& iter, DistT d, std::input_iterator_tag)    {        if( d < 0 ){            throw std::out_of_range( "Negative distance" );        }        while( d-- ){            ++iter;        }    }


    有了这些doAdvance重载版本,advance需要做的只是调用它们并额外传递一个对象,后者必须带有适当的迭代器分,于是编译器运用重载机制调用适当的实现代码:
    template<typename IterT,typename DistT>    void advance( IterT& iter, DistT& d ){        doAdvance( iter, d, typename std::iterator_traits<IterT>::iterator_category() );    }

    啦啦啦啦,搞定!现在我们来总结一下如何使用一个traits class了:
    ■ 建立一组重载函数或模板函数,彼此之间差异只在于各自的traits参数.令每个函数实现码与其接受之traits信息相应和.    
    ■ 建立一个控制函数或函数模板,它调用上述重载函数并传递traits class所提供的信息.
    好了,今天任务完成!
    请记住:
    ■ Traits classes使得'类型相关信息'在编译期可用.它们以templates和'templates特化'完成实现.
    ■ 整合重载技术后,traits classes有可能在编译期对类型执行if...else测试.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4714971>

四十八、模板元编程

条款48:认识template元编程
    Be aware of template metaprogramming.

    Template metaprogramming(TMPS,模板元编程)是编写template-based C++程序并执行编译期的过程.它本质上是以C++写成、执行于C++编译器内的程序.一旦TMP程序结束执行,其输出,也就是从templates具现出来的若干C++源码,便会一如往常地被编译.TMP有两个伟大的效力.第一,它让某些事情更容易.如果没有它,那些事情将是困难,甚至不可能.第二,欲与TMP执行于C++编译期,因此可将工作从运行期转移到编译期.这导致一个结果是,某些错误原本通常在运行期才能检测到,现在可在编译期找出来.这样带来的结果是很美好的.因为使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存要求.然而它也会带来另一个令人感到不愉快的结果是,编译时间变长了.前面一款我们提到了使用typeid的advance的伪代码实现版本:

    template <typename IterT, typename DistT>    void advance( IterT& iter, DistT d )    {        if( typeid( typename std::iterator_traits<IterT>::iterator_category )             == typeid( std::random_access_iterator_tag ) ){            iter += d;        } else {            if( d >= 0 ){                while( d-- ){                    ++iter;                }             } else {                while( d++ ){                    --iter;                }            }        }    }


    上一条款中我曾提过advance的typeid-based实现方式可能导致编译期问题,我们现在就写出如下代码测试:
    std::list<int>::iterator iter;    ...    advance( iter, 10 ); //compile error:error C2676: binary '+=' : 'std::list<_Ty>::    //_Iterator<_Secure_validation>' does not define this operator or a conversion to     //a type acceptable to the predefined operator.


    上述错误信息显示的是iter += d;由于这行代码导致的.这怎么可能?list<int>::iterator是bidirectional迭代器,并不支持+=,只有random access迭代器才支持,此刻我们知道编译器绝对不会执行+=那一行代码的,因为测试typeid那行的if语句总是不成立.你开始郁闷了吧?我在这里要说的是:在用TMP编写的C++程序中,编译器必须确保所有源码都有效,纵使是不会执行的代码.晕,原来还有这样的啊,其实你仔细想一想,这样做也是可以理解的:TMP程序被具现化以后发生在编译期,编译器必须依靠执行的具现化代码才能确定哪些语句能执行到,为了对所有可能的具现化代码编译的支持,编译器也只能这样做!葛大爷在他的一部影片中说了一句:'有枣没枣打一杆,宁可错杀一千,不愿放走一个.',要是用在这里的话也有一定的道理,呵呵.
    为求领悟TMP之所以值得学习,很重要一点是先对它能够表达的目标有一个比较好的理解,原书上对下面的每一个点都举了例子进行阐述,我对这些阐述还没有理解的足够的深刻,所以在这里我也不敢误人子弟地阐述自己的理解我只帖出来原书上出现的三个TMP运用的例子,具体的阐述过程,我建议各位还是看原书上的阐述吧!这三个例子为:
    ■ 确保度量单位正确.
    ■ 优化矩阵运算.
    ■ 可以生成客户定制之设计模式实现品.

    TMP或许永远不会成为主流,但对某些程序员--特别是程序库开发人员---几乎确定成为他们的主要粮食.
    请记住:
    ■ TMP可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率.
    ■ TMP可别用来生成'基于政策选择组合'的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4717077>

(八)、定制new和delete

四十九、new-handler行为

条款49:了解new-handler的行为
    Understand the behavior of the new-handler.
    
    在我们平时的编写代码过程中,总是避免不了去new出一些对象出来.我们知道new操作符私底下通过调用operator new来实现内存分配的.当operator new抛出异常以反映一个未获满足的内存需求之前,它会调用一客户指定的错误处理函数,一个所谓的new-handler.而客户是通过set_new_handler将自己的new-handler传递给它的,其声明在<new>标准库函数中:

    namespace std{        typedef void (*new_handler)();        new_handler set_new_handler( new_handler p ) throw();    }
    当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够内存.关于反复调用代码我们在条款51中我们会讨论.一个设计良好的new-handler函数必须做以下事情:
    ■ 让更多内存可被使用.
    ■ 安装另一个new-handler.
    ■ 卸除new-handler
    ■ 抛出bad_alloc(或派生自bad_alloc)的异常.
    ■ 不返回.

    这些选择让你在实现new-handler函数时拥有很大的弹性.而有时候你会希望'依据不同的类,调用附属与该类的new-handler,看起来也许是这个样子:
    struct TestA{        static void outOfMemory();        ...    };    struct TestB{        static void outOfMemory();        ...    };    TestA* a = new TestA;//if memory runs out,call TestA::outOfMemory    TestB* b = new TestB;//if memory runs out,call TestB::outOfMemory    
    但是C++不支持class专属之new-handlers,但你可以自己实现这种行为.好,我们来试试!假设我们现在打算处理Widget class的内存分配失败情况,我们需要声明一个类型为new_handler的static成员,用以指向class Widget的new-handler,看起来应该像这样:
    struct Widget{        static std::new_handler set_new_handler( std::new_handler p )throw();        static void* operator new( std::size_t  size) throw(std::bad_alloc);    private:        static std::new_handler st_current_handler_;    };    Widget内的set_new_handler函数会将它获得的指针存储起来,然后返回调用之前的指针,这跟标准版set_new_hander的行为是一样的:    std::new_handler Widget::set_new_handler( std::new_handler p ) throw (){        std::new_handler old_handler = st_current_handler_;        st_current_handler_ = p;        return old_handler;    }

    下面我将构造一个资源处理类来操作new-handler,在构造过程中获得资源,在析构过程中释放:
    struct NewHandlerHolder{        explicit NewHandlerHolder(std::new_handler handler)            :handler_( handler ){}        ~NewHandlerHolder(){            std::set_new_handler( handler_ );        }    private:        NewHandlerHolder(const NewHandlerHolder&);        NewHandlerHolder& operator=(const NewHandlerHolder&);        std::new_handler handler_;            };
    这样Widget内的operator new的实现就变的容易了:
    void* Widget::operator new(std::size_t size) throw(std::bad_alloc)    {        NewHandlerHolder alloc_handler( std::set_new_handler( st_current_handler_ ) );        return ::operator new( size );    }
    客户现在可以这样用:
    void outOfMemory();       Widget::set_new_handler( outOfMemory );    Widget* new_widget_obj = new Widget; //if memory allocation failed,                                      //call outOfMemory    std::string* new_string = new std::string; //if memory allocation failed,                                            //call global new-handling function                                            //if exists.                                                   Widget::set_new_handler( 0 );    Widget* another_widget = new Widget; // if memory allocation failed,throw                                      //exception at once.     
        
    其实我们可以用base class将new-handler操作独立出来,让Widget继承自base class,呵呵,这样是
不是更好:
    template<typename T>    struct NewHandlerSupport{        static std::new_handler set_new_handler( std::new_handler p ) throw (){            std::new_handler old_handler = st_current_handler_;            st_current_handler_ = p;            return old_handler;        }        static void* operator new( std::size_t size ) throw ( std::bad_alloc ){            NewHandlerHolder h( std::set_new_handler( st_current_handler_ ) );            return ::operator new( size );        }        ...    private:        static std::new_handler st_current_handler_;    };    template<typename T>    std::new_handler NewHandlerSupport<T>::st_current_handler_ = 0;


    有了这个template,为Widget添加set_new_handler支持能力就轻而易举了:
    struct Widget:public NewHandlerSupport<Widget>{        ...    };


    好了,这个问题的讨论,就结束了.我们来把目光转到Nothrow new.
    nothrow版的operator new被调用,用以分配足够内存给Widget对象.如果分配失败便返回null指针,一如文档所言.所有分配成功,接下来Widget构造函数会被调用,而在那一点上所有的筹码便被耗尽,因为Widget构造函数可以做它想做的任何事情.它有可能又new一些内存,而没人可以强迫它再次使用nothrownew.现在我们可以得出结论:使用nothrow new只能保证operaor new不抛掷异常,不保证像'new (std::nothrow) Widget'这样的表达式不导致异常,其实你没有运用nothrow new的需要.
    今天的讨论就到这里,我们下款再见!
    请记住:
    ■ set_new_handler允许客户指定一个函数,在内存分配无法获得满足时调用.    
    ■ nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常.

(这一节看的很勉强==!)
来源: <http://blog.csdn.net/scofieldzhu/article/details/4729675>
 

五十、newdelete合理替换时机

条款50:了解new和delete的合理替换时机
    Understand when it makes sense to replace new and delete.
    
    怎么会有人想要替换编译器提供的operator new或operator delete呢?我们可以列出如下三个常见的理由:
    ■ 用来检测运用上的错误.
    程序员从开始编写代码到调试直至最终完成,这一过程当中犯下各种各样的错误在所难免,这些错误就可能导致内存泄露(memory leaks)、不确定行为产生、overruns(写入点在分配区块尾端之后)、underruns(写入点在分配区块尾端之前)等不良结果的发生.如果我们自定义operator news,我们就可以超额分配内存,用额外空间放置特定签名来检测类问题.
    ■ 为了强化效能.
    我们所用的编译器中自带的operator new和operator delete主要是用于一般的目的能够为各种类型的程序所接受,而不考虑特定的程序类型.它们必须处理一系列需求,必须接纳各种分配形态,必须要考虑破碎问题等等这些问题,因此编译器所带的operator new和operator delete采取中庸之道也是没办法的事情.它们的工作对每个人都是适度地好,但不对特定任何人有最佳表现.通常可以发现,定制版之operator new和operator delete性能胜过缺省版本.所谓的'胜过',就是它们比较快,有时甚至快很多,而且它们需要内存比较少,最高可省50%,所以说对某些运用程序而言,将缺省new和delete替换为定制版本,是获得重大效能提升的办法之一.
    ■ 为了收集使用上的统计数据.
    收集你的软件如何使用其动态内存.分配区块的大小发布如何?寿命发布如何?它们倾向于以FIFO次序或LIFO次序或随机次序来分配和归还?它们的运用形态是否随时间改变,也就是说你的软件在不同执行阶段有不同的分配/归还形态吗?任何时刻所使用的最大动态内存分配量是多少?自行定义的operator new和operator delete使我们得以轻松收集这些信息.
    基于上述三种理由,我不得不开始了写一个定制型operator new了,我的初稿看起来如下述代码所示,其中还存在不少小错误,稍后我会完善它.

    
typedef const int signature = 0xDEADBEEF;    typedef unsigned char Byte;    void* operator new( std::size_t size)throw(std::bad_alloc)    {        using namespace std;        size_t real_size = size + 2 * sizeof(int);                void* applied_memory = malloc( real_size );        if( applied_memory == 0 ){            throw bad_alloc();        }        //将signature写入内存的最前段落和最后段落.        *(static_cast<int*>( applied_memory ) = signature;        *(reinterpret_cast<int*>(static_cast<Byte*>( applied_memory ) + real_size - sizeof(int) ) ) = signature;        //返回指针,指向恰位于第一个signature之后的内存位置.        return static_cast<Byte*>( applied_memory ) + sizeof(int);    }


    我刚才说了,这个版本有不少问题.而现在我只想专注一个比较微妙的主题:齐位(alignment).关于齐位的具体是什么? 我假设大家都已经知道了,我在这里就不唠叨讲了.因为C++要求所有operator news返回的指针都有适当的对齐(取决于数据类型).malloc就是在这样的要求下工作的,所以令operator new返回一个得自malloc的指针是安全地.然而上述operator new中我并未返回一个得自malloc的指针,而是返回一个得自malloc且偏移一个int大小的指针.没有人能够保证它的安全!我们可能因使用该版本的operator new导致获得一个未有适当齐位的
指针.那可能会造成程序崩溃或执行速度变慢.不论那种情况都不是我们希望看到的结果.
    写一个总是能够运行的内存管理器并不难,难的是它能够优良地运作.一般而言,本书的作者建议你在必要的稍后才试着写写看.很多时候这是非必要的.某些编译器已经在它们的内存管理函数中切换至调试状态和志记状态.快速浏览一下你的编译器文档,很可能就消除了你自行写new和delete的需要了.
    另一个选择是开放源码领域中的内存管理器.它们对许多平台都可用,你可以下载试试.Boost程序库的Pool是这样一个分配器,它对于常见的'分配大量小型对象'很有帮助.TR1支持各种类型特定对齐条件,很值得注意.
    讨论到这里,我们又可以为本款开头讨论的问题理由再添加几条了,呵呵:

    ■ 为了增加分配和归还的速度.
    泛用型分配器往往比定制型分配器慢,特别是当定制型分配器专门针对某特定类型之对象而设计时.
    ■ 为减低缺省内存管理器带来的空间额外开销.
    泛用型内存管理器往往还使用更多的内存,那是因为它们往往常常在每一个分配区块身上招引某些额外开销.
    ■ 为了弥补缺省分配器中的非最佳齐位.
    ■ 为了将相关对象成簇集中(详略).
    ■ 为了获得非传统行为(详略).

    OK,our topic talk is over!
    请记住:
    ■ 有许多理由需要写个自定义的new和delete,包括改善效能,对heap运用错误进行调试,收集heap使用信息.
来源: <http://blog.csdn.net/scofieldzhu/article/details/4732367>
 
五十一、newdelete固守常规

条款51:编写new和delete时需固守常规
    Adhere to convention when writing new and delete.
    
    大家好,前一条款我们已经讨论了你在什么时候想要写个自定义的operator new和operator delete,但并没有解释当你这么做时必须遵守什么规则.我先来总体说一下,然后再分析这些规则.
    要实现一致性operator new必得返回正确的值,内存不足时必得调用new-handling函数(见条款49),必须有对付零内存需求的准备,还需避免不慎掩盖正常形式的new(虽然这比较偏近class的接口要求而非实现要求).
    先来说说关于其返回值,如果它有能力供应客户申请的内存,就返回一个指针指向那块内存.如果没有那个能力,就遵循49描述的原则,并抛出一个bad_alloc异常.
    而实际上operator new不止一次尝试进行内存分配,并在每次失败后调用new-handing函数.这里假设new-handling函数也许能够做某些动作释放某些内存.只有当当前的new-handling函数指针为null时,operator new才会抛出异常.
    C++规定,即使客户要求0 bytes,operator new也得返回一个合法指针.这种诡异的行为其实是为了简化语言的其它部分.下面是个non-member operator new伪码:

    void* operator new(std::size_t size) throw(std::bad_alloc)    {        using namespace std;        if( size == 0 ){            size = 1;        }        while( true ){            ...//try to allocate size bytes memory.             if( allocate_succeed ){                return (point_to_allocted_memory);            }            //allocate failed:find current new-handling function(as following)            new_handler global_handler = set_new_handler( 0 );            set_new_handler( global_handler );                        if( global_handler ){                ( *global_handler )();            } else {                throw std::bad_alloc();            }        }    }


    现在我们注意一下这里的一个可能会出现的问题:很多人没有意识到operator new成员函数会被derived classes继,那就会出现,有可能base class的operator new被调用用以分配derived class对象:
    struct Base{        static void* operator new(std::size_t size) throw( std::bad_alloc );        ...    };    struct Derived:public Base{...};    Derived* p = new Derived;//call Base::operator new.


    如果Base class专属的operator new并非被设计对付上述情况(实际上往往如此),处理此情势的最佳做法是将'内存申请量错误'的调用行为改采标准operator new,像这样:
    void* Base::operator new(std::size_t size) throw(std::bad_alloc)    {        if( size != sizeof(Base) ){            return ::operator new( size ); //call standard operator new version.        }        ...    }
    如果你打算控制class专属之'arrays内存分配行为',那么你需要实现operator new的array兄弟版:operator new[].这个通常被称为"array new".如果你要写这个operator new[],记住,唯一需要做的事情就是分配一块未加工内存.你无法对array之内迄今为止尚未存在的元素对象做任何事情.实际上你甚至无法计算这个array将含有多少个元素.首先你不知道每个对象多大,因此你不能在Base::operator new[]内假设每个元素对象大小是sizeof(Base),此外传递给它的参数size的值有可能比'将被填以对象'的内存数量更多.
    这就是写operator new时候你需要奉行的规矩.operator delete情况更简单,你需要记住的唯一一件事情就是C++保证'删除null指针永远安全',所以你必须兑现这项保证.下面就是non-member operator delete的伪码:
    void operator delete( void* raw_memory ) thrwo()    {        if( raw_memory == 0 ){            return;        }        ...//now,free raw memory block.    }</span>
    而对于member版本的也很简单.
<span style="font-size:14px;">    void Base::operator delete( void* raw_memory,std::size_t size ) throw()    {        if( raw_memory == 0 ){            return;        }        if( size != sizeof(Base) ){ //if size error, call standard operator delete            ::operator delete( raw_memory );            return;        }        ...//now,free your raw memory block.        return;    }
    如果即将删除的对象派生自某个base class而后者欠缺virtaul析构函数,那么C++传给operator delete的size_t数值可能不正确.这是'让你的base class拥有virtual析构函数'的一个够好的理由.
    好了,今天讨论结束,明天再见.
    请记住:
    ■ operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler.它也应该有能力处理0 bytes申请.class专属版本则还应该处理'比正确大小更大的(错误)申请'.
    ■ operator delete应该在收到null指针时不做任何事情.class专属版本则还应该处理'比正确大小更大的(错
误)申请
'.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4733850>
 

五十二、写了placement new就要写placement delete

条款52:写了placement new也要写placement delete
    Write placement delete if you write placement new.

    我们都知道当你在写一个new表达式像这样:
    Widget* new_widget = new Widget;
    共有两个函数被调用:一个是用以分配内存的operator new,一个是Widget的default构造函数.
    那么假设我们现在遇到的情况是:第一个函数调用成功,第二个函数却抛出异常.按照常理,在步骤一中所分配的内存必须取消,否则就会造成内存泄露.而在这个时候客户已经没有能力归还内存了,因为手上没有指向这块内存的指针,故此任务就落到了C++运行期系统身上.
    为了完成任务,运行期系统当然就会调用步骤一所用的operator new的相应operator delete版本.如果当前要处理的是拥有正常签名的new和delete版本,这好办!因为正常的operator new签名式:

    void* operator new(std::size_t) throw (std::bad_alloc);</span>

    对应的正常的operator delete签名式:
    void operator delete(void* raw_memory)throw();//global 作用域中的正常签名式    void operator delete(void* raw_memory,std::size_t size)throw();//class作用域中典型的签名式


    因此,当你只使用正常形式的new和delete,运行期系统毫无问题可以找出那个'知道如何取消new所作所为并恢复旧观'的delete.然而当你开始声明非正常形式的operator new,即就是附加参数的operator new,问题就出来了.
    为了说明这个问题,我们依然用Widget例子,假设你写了一个class专属的operator new,要求接受一个ostream,用来logged相关分配信息,同时又写了一个正常形式的class专属operator delete:
    struct Widget{        //非正常形式的new        static void* operator new(std::size_t size,std::ostream& log_stream)throw(std::bad_alloc);        //正常class专属delete.        static void  operator delete(void* memory, std::size_t size)throw();        ...    };


    在这里我们定义:如果operator new接受的参数除了一定会有的那个size_t之外还有其它,这个便是所谓的placement new.而众多的placement new版本中特别提到的是'接受一个指针指向对象该被构造之处',那样的operator new长相如下:
    void* operator new( std::size_t, void* memory ) throw();//placement new</span>
    该版本的new已经被纳入C++标准程序库,你只要#include <new>就可以取用它,它的用处就是负责在vector的未使用空间上创建对.
    现在让我们回到Widget的声明式,这个Widget将引起微妙的内存泄漏.考虑下面的测试代码,它将在动态创建一个Widget时将相关的分配信息志记与cerr:
   Widget* new_widget = new( std::cerr ) Widget;</span>
    在说一下我们先前提到的问题,如果内存分配成功,而构造抛出异常,运行期就有责任取消operator new的分配并恢复旧观.然而运行期系统无法知道真正被调用的那个operator new如何运作,因此它无法取消分配并恢复旧观,所以上述做法行不通.取而代之的是,运行期系统寻找'参数个数和类型都与operator new相同'的某个operator delete.如果找打,那就是它该调用的对象.既然这里的operator new接受的类型为ostream&的额外实参,所以对应的operator delete就应该是:
    void operator delete(void*,std::ostream&)throw();</span>
    类似于new的placement版本,operator delete如果接受额外参数,便称为placement deletes.现在,既然Widget没有声明placement版本的operator delete,所以运行期系统不知道如何取消并恢复原先对placement new的调用.于什么也不做.本例之中如果构造抛出异常,不会有任何operator delete被调用.
    为了消除Widget中的内存泄漏,我们来声明一个palcement delete,对应与那个有志记功能的placement new:
    struct Widget{        static void* operator new(std::size_t size, std::ostream& log_stream)throw(std::bad_alloc);        static void  operator delete(void* memory) throw();        static void  operator delete(void* memory,std::ostream& log_stream)throw();        ...    };
    这样改变之后,如果以下语句引发Widget构造函数抛出异常:
    Widget* new_widget = new (std::cerr) Widget; //一如既往,但这次就不在发生泄漏.</span>
    然而如果没有抛出异常(大部分是这样的),客户代码中有个对应的delete,会发生什么事情:
    delete pw; //call normal operator delete</span>

    调用的是正常形式的operator delete,而非其placement版本.请记住:placement delete只有在'伴随placement new调用而触发的构造函数'出现异常时才会被调用.对着一个指针施行delete绝不会导致调用placement delete.
    还有一点你需要注意的是:由于成员函数的名称会遮盖其外围作用域中的相同名称,你必须小心避免让class专属news遮盖客户期望的其它news(包括正常版本).默认情况下,C++在global作用域内提供以下形式的operator new:
    void* operator new(std::size_t)throw(std::bad_alloc);//normal new.    void* operator new(std::size_t,void*)throw();//placement new    void* operator new(std::size_t, const std::nothrow_t&)throw();//nothrow new.see Item 49.
    如果你在class内声明任何operator news,它会遮掩上述这些标准形式.除非你的意思就是要阻止class的客户使用这些形式,否则请确保它们在你所生成的任何定制型operator new之外还可用.对于每一个可用的operator new也确定提供对应的operator delete.如果希望这些函数都有着平常的行为,只要令你的class专属版本调用global版即可.
    为了完成以上所言的一个简单做法就是建立一个base class,内含所有正常形式的new和delete:
    struct StandardNewDeleteForms{        //normal new/delete        static void* operator new(std::size_t size)throw(std::bad_alloc)        { return ::operator new( size ); }        static void operator delete(void* memory) throw()        { ::operator delete( memory ); }        //placement new/delete        static void* operator new(std::size_t size,void* pointer)throw()        { return ::operator new( size, pointer );}        static void operator delete(void* memory,void* pointer)throw()        { return ::operator delete( memory, pointer ); }        //nothrow new/delete        static void* operator new(std::size_t size,const std::nothrow_t& no_throw)throw()        { return ::operator new( size, no_throw ); }        static void operator delete(void* memory,const std::nothrow_t&)throw()        { ::operator delete( memory ); }    };
    凡是想自定形式扩充标准形式的客户,可利用继承机制及using声明式(Item 33)取得标准形式:
    struct Widget:public StandardNewDeleteForms{        using StandardNewDeleteForms::operator new;        using StandardNewDeleteForms::operator delete;        static void* operator new(std::size_t size, std::ostream& log_stream) throw(std::bad_alloc);        static void  operator delete(void* memory,std::ostream& log_stream) throw();        ...    };
    好了,本款讨论结束.
    请记住:
    ■ 当你写一个placement operator new,请确定也写出了对应的placement operator delete.如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄漏.
    ■ 当你声明placement new和placement delete,请确定不要无意识地遮掩它们的正常版本.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4737353>
 

(九)、杂项

五十三、不要忽视警告

条款53:不要轻忽编译器的警告
    Pay attention to compiler warnings.

    许多程序员习惯性地忽略编译器警告.他们任务如果是问题真的很严重的话,编译器就应该给一个错误提示信息而不是警告信息.这种想法看起来似乎很合理,但是在C++语言上,我觉得编译器作者对代码即将会爆发的事情应该比你有更加深入的理解,你说呢?下面我举的这个例子是多多少少在大部人身上都发生过一个错误:

    struct B{        virtual void f()const;    };    struct D:public B{        virtual void f();    };
    我现在的编译器给出了这样的警告信息:warning:D::f()hides virtual B::f()
    大部分经验不足的程序员对这个信息的反映都是一副不以为然的态度,他们认为,D::f遮掩了B::f显然是发生了字遮掩现象,给出这样的信息很正常啊!错,此刻的编译器是在试图告诉你声明于B中的f并未在D中被重新声明,而是被整个遮掩了(Item 33描述为什么会遮掩).如果忽略这个警告,几乎肯定导致错误的程序行为,然后是许多的调试行为,只为了找出编译器早就试图告诉你的事情.
    你应该知道,编译器给出的信息往往和它们'看起来'的意义十分不同!不管怎么样,在你打发某个警告信息之前,请确定你了解它试图说出的精确意义.It is very important.
    你也应该知道,警告信息天生和编译器相依,不同的编译器有不同的警告标准.所以草率编程后依赖编译器为你指出错误,是非常不明智的选择.
    请记住:
    ■ 严肃对待编译器发出的警告信息.努力在你i的编译器的最高(最严厉)警告级别下争取'无任何警告'的荣誉。        
    ■ 不要过度依赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同,一旦移植到另一个编译器上,你原
本依赖的警告信息有可能消失.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4738067>
 

五十四、熟悉TR1标准库

  条款54:让自己熟悉包括TR1在内的标准程序库
    Familiarize yourself with the standard library,including TR1.

    本款原书主要介绍了标准程序库和TR1程序库.书里面介绍的很详细,我在这里就不想再次累赘介绍了,有兴趣的可以阅读原书的内容.我只把原书中关于本款要记住的要点帖出来:
    请记住:
    ■ C++标准程序库的主要机能是由STL,iostreams,locales组成.并包含C99标准程序库.
    ■ TR1添加了智能指针(例如tr1::shared_ptr)、一般化函数指针(tr1::function)、hash-based容器正则表达式(regular expressions)以及另外10个组件的支持.
    ■ TR1自身只是一份规范.为获得TR1提供的好处,你需要一份实物.一个好的实物来源是Boost.

来源: <http://blog.csdn.net/scofieldzhu/article/details/4738137>
 

五十五、熟悉Boost

条款55(最一款):让自己熟悉Boost
    Familiarize yourself with Boost.

    本款是本书的最后一款,主要介绍了Boost库.书里面介绍的很详细,我在这里就不想再次累赘介绍了,有兴趣的可以阅读原书的内容.我只把原书中关于本款要记住的要点帖出来.
    请记住:
    ■ Boost是一个社群,也是一个网站.致力于免费、源码开发、同僚复审的C++程序库开发.Boost在C++标准化过程过程中扮演深具影响力的角色.
    ■ Boost提供许多TR1组件实现品,以及其他许多程序库.


来源: <http://blog.csdn.net/scofieldzhu/article/details/4738184>
 

0 0
原创粉丝点击