博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

effective C++ 55 26-40笔记

Posted on 2011-07-08 20:55  李大嘴  阅读(423)  评论(0编辑  收藏  举报

条款26: 尽可能延后变量定义式的出现时间
01    //方案A和B哪个比较好?
02    //方案A
03    Widget w;
04    for (int i = 0; i < n; ++i) {
05        w=...;
06    }
07     
08    //方案B
09    for (int i = 0; i < n; ++i) {
10        Widget w(...);
11    }

方案A:1次构造,1次析构,n次赋值

方案B:n次构造,n次析构

除非你知道(1)赋值成本小于构造+析构   (2)你正在处理代码中效率高度敏感的部分

否则应该选择方案B

 

条款27:尽量少做转型动作
01    (T)expression  //C风格
02    T(expression)  //函数风格
03     
04    //C++新式转型
05    const_cast<t>(expression)   //常量性移除,唯一有此能力的转型
06    dynamic_cast<t>(expression) //安全向下转型,唯一无法由旧时语法执行的动作
07    reinterpret_cast<t>(expression)//低级转型,实际动作可能取决于编译器,如int*转为int,条款50。没有看到类型转化兼容性的检查
08    static_cast<t>(expression)  //强迫隐式转型,non-const转const,int转double,void*转typed指针,pointer to base转pointer to derived
09     
10    //最好唯一使用旧时转型的时机: 调用explicit构造函数将对象传给函数
11    class Widget {
12    public:
13      explicit Widget(int size);
14    };
15    void doSomeWork(const Widget& w);
16    doSomeWork(Widget(15));                  
17    doSomeWork(static_cast<widget>(15));       // create Widget from int with C++-style cast
18    </widget></t></t></t></t>

 

转型不是告诉编译把某种类型视为另一种类型! 任何一个类型转换往往真的另编译器编译出运行期执行的码
1    int x, y;
2    double d = static_cast<double>(x)/y; //用浮点除法
3     
4    Dereved d;
5    Base *pb = &d;  //Derived* 转为Base*
6                    //两个指针指可能不同,可能会有个偏移量offset在运行期被施于Derived指针上,用以取得正确的Base*
7    </double>

单一对象(如一个Derived对象),可能拥有一个以上的地址(如以Base*指向的地址和以Derived*指向的地址)。

因此,避免做出对象在C++中如何布局的稼穑,更不改以此假设为基础执行任何转型动作。如将对象地址转为char*,然后在上面进行指针算术,几乎总会未定义。
01    class Window {                             
02    public:
03      virtual void onResize() { ... }     
04      ...
05    };
06    class SpecialWindow: public Window {
07    public:
08      virtual void onResize() {
09        static_cast<window>(*this).onResize(); //error,在当前对象的base class成分的副本上调用Window::onResize()
10        ...                 //  当前对象上执行SpecialWindow专属动作
11      }
12    };
13    //解决之道
14    class SpecialWindow: public Window {
15    public:
16      virtual void onResize() {
17        Window::onResize();                    // call Window::onResize
18        ...                                    // on *this
19      }
20      ...
21    };
22     
23     
24    </window>

dynamic_cast通常是你想在一个你认定为derived class对象上执行derived class操作函数,但你手上只有base*或引用。

可使用类型安全容器,或将virtual函数往继承体系上方移动,替换dynamic_cast转型。  不要使用连串dynamic_cast。

 

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

避免返回handles(包括引用,指针,迭代器)指向内部数据。遵守这个条款可以增加封装性,帮助const成员函数像个const,并将发生悬吊号码牌dangling handles的可能性降至最低。

但不意味绝对不可以让成员函数返回handles。如operator[]允许你摘采string,vector的个别元素,这些数据会随着容器的销毁而销毁。而这样的函数只是例外。
01    class Point {
02    public:
03      Point(int x, int y);
04      void setX(int newVal);
05      void setY(int newVal);
06      ...
07    };
08     
09    struct RectData {
10      Point ulhc;         // ulhc = " upper left-hand corner"
11      Point lrhc;        // lrhc = " lower right-hand corner"
12    };
13     
14     
15    class Rectangle {
16      ...
17    private:
18      std::tr1::shared_ptr<rectdata> pData;
19    };                                         
20     
21    class Rectangle {
22    public:
23      Point& upperLeft() const { return pData->ulhc; }
24      Point& lowerRight() const { return pData->lrhc; }
25    };
26     
27    Point coord1(0, 0);
28    Point coord2(100, 100);
29    const Rectangle rec(coord1, coord2);   
30    rec.upperLeft().setX(50);   //rec是const,upperLeft是const函数,但内部数据被更改了
31     
32    //解决方案,向外传递内部数据引用的成员函数申明为const
33    class Rectangle {
34    public:
35      const Point& upperLeft() const { return pData->ulhc; }
36      const Point& lowerRight() const { return pData->lrhc; }
37    }
38    //但依然有问题,可能导致空悬的号码牌dangling handles: handles所指东西(的所属对象)不复存在
39    //考虑下例
40    class GUIObject { ... };
41    const Rectangle boundingBox(const GUIObject& obj);
42     
43    GUIObject *pgo;
44     
45    const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());
46    //boundingBox返回一个const Rectangle临时对象temp,然后upperLeft返回temp的一个const Point引用
47    //然后另pUpperLeft指向那个Point
48    //但是当这个语句结束后,临时对象temp被销毁,导致temp内的Points析构,pUpperLeft成为悬垂指针
49    </rectdata>

 

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

以对象管理资源管理资源解决资源泄漏,然后根据“现实可施作”条件下挑选3个异常安全保证中的最强烈等级,只有在你的函数调用了传统代码,才别无选择的设为无任何保证。
01    class PrettyMenu {
02    public:
03      void changeBackground(std::istream& imgSrc);
04    private:
05      Mutex mutex;
06      Image *bgImage;
07      int imageChanges;
08    };
09     
10    void PrettyMenu::changeBackground(std::istream& imgSrc)
11      lock(&mutex);
12      delete bgImage;
13      ++imageChanges;
14      bgImage = new Image(imgSrc);
15      unlock(&mutex);
16    }

异常安全的两个条件:当异常抛出时

    不泄露任何资源:  new Image(imgSrc)抛出异常,unlock(&mutex); 就不会执行了
    不允许数据败坏: new Image(imgSrc)抛异常, bgImage就指向已被删除的对象,imageChanges也已被增加

1    //避免资源泄露,条款13,14,以对象管理资源
2    void PrettyMenu::changeBackground(std::istream& imgSrc){
3      Lock ml(&mutex);
4      delete bgImage;
5      ++imageChanges;
6      bgImage = new Image(imgSrc);
7    }

异常安全函数提供三个保证:

    基本承诺: 如果异常被抛出,程序内任何事物仍然保持在有效状态下。程序有可能处于任何状态--只要该状态合法。
    强烈保证: 如抛出异常,程序状态不改变。如果函数成功,就是完全成功;函数是不程序会回复到调用函数之前的状态。
    不抛掷(nothrow)保证: 承诺绝不抛出异常,因为它们总是能为完成它们原先承诺的功能

1    int doSomething() throw(); //空白的异常明细,如果抛出异常,则会调用unexpected(),其中调用terminate(),可用set_expected()设置默认函数

 
01    class PrettyMenu {
02      ...
03      std::tr1::shared_ptr<Image> bgImage;
04      ...
05    };
06    void PrettyMenu::changeBackground(std::istream& imgSrc) {
07      Lock ml(&mutex);
08      bgImage.reset(new Image(imgSrc));//delete在reset内部被调用,没进入reset则不会调用,提供强烈保证
09      ++imageChanges;
10    }

美中不足的imgSrc:如果Image构造函数抛出异常,有可能输入流input stream的读取记号read marker(如读取指针)已被移动,而这样的搬移对程序其余部分是一种可见的状态改变。再读imgSrc就从先前的后面开始读取。
所以changeBackground在解决这个问题前只提供基本的异常安全保证。

 

强烈保证另一种方案: copy and swap  创建副本,对副本操作,若有异常,原始对象不变,若成功再在不抛出异常的swap中交换
01    //pimpl idiom手法:将隶属于对象的数据从原对象放进另一对象内,然后赋予原对象一个指针指向那个所谓的实现对象。条款31
02    struct PMImpl {
03      std::tr1::shared_ptr<Image> bgImage;
04      int imageChanges;
05    };
06    class PrettyMenu {
07    private:
08      Mutex mutex;
09      std::tr1::shared_ptr<pmimpl> pImpl;
10    };
11    void PrettyMenu::changeBackground(std::istream& imgSrc) {
12      using std::swap;                            // see Item 25
13      Lock ml(&mutex);                            // acquire the mutex
14      std::tr1::shared_ptr<pmimpl>                // copy obj. data
15        pNew(new PMImpl(*pImpl));
16      pNew->bgImage.reset(new Image(imgSrc));     // modify the copy
17      ++pNew->imageChanges;
18      swap(pImpl, pNew);                          // swap the new
19    }                                             // release the mutex
20    </pmimpl></pmimpl>

copy and swap 不保证整个函数有强烈的异常安全。
1    void someFunc() {
2        ...               //对local状态做一份副本
3        f1();           
4        f2();
5        ...               //将修改后的状态置换回来
6    }

如果f1或f2异常安全性比强烈保证低,则someFunc很难获得强烈安全。

如果f1或f2都是强烈异常安全的,情况并不就此好转。如果f1圆满结束,程序状态有所改变。随后f2异常,程序状态和someFunc被调用前不会相同。

 

copy and swap的创建副本可能耗用无法供应的时间和空间,所以”强烈保证”并非在任何时刻都显得实际。所以有时候基本保证是个绝对通情达理的选择。

 

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

inline函数: 对此函数的每一个调用都以函数本体替换之。 因此可能增加目标码object code的大小,在一台内存有限的机器上,过度热衷inline会造成程序体积过大,即使拥有虚内存,inline造成的代码膨胀亦会导致额外的换页行为,降低指令高速缓存装置的击中率,以及伴随这些而立的效率损失。

而如果inline函数本体很小,编译器针对函数本体所产出的码可能针对函数调用所产出的码更小,可导致较小的目标码和较高的指令高速缓存装置击中率。

 

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

隐式声明inline: 将函数定义于class定义式内。  显示声明: 在函数定义式前加inline。

inline在大多数C++程序中是编译期行为。

如果你在写一个template而你认为所有根据次template具现出来的函数都应该为inline,则将此template声明为inline。否则不声明。

大多数编译期拒绝太过复杂(循环或递归)的函数inlining,而所有virtual函数调用(除非是最平淡无奇的)也都会使inlining落空(运行期行为)。

如果程序要取某个inline函数的地址或指针调用函数,编译器通常不对该函数实施inlining。

 

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

当你使用new时,动态创建的对象被其构造函数自动初始化;当你使用delete时,析构函数被调用;

当你创建一个对象,其每一个base class及每一个成员变量都会被自动构造;当你销毁一个对象,反向程序的析构行为会自动发生;

如果有个异常在对象构造期间被抛出,该对象已构造好的那一部分会被自动销毁。
01    Derived::Derived(){      //空白Derived构造函数的观念性实现
02     Base::Base();                           // initialize Base part
03     try { dm1.std::string::string(); }      // try to construct dm1
04     catch (...) {                           // if it throws,
05       Base::~Base();                        // destroy base class part and
06       throw;                                // propagate the exception
07     }
08     try { dm2.std::string::string(); }      // try to construct dm2
09     catch(...) {                            // if it throws,
10       dm1.std::string::~string();           // destroy dm1,
11       Base::~Base();                        // destroy base class part, and
12       throw;                                // propagate the exception
13     }
14     try { dm3.std::string::string(); }      // construct dm3
15     catch(...) {                            // if it throws,
16       dm2.std::string::~string();           // destroy dm2,
17       dm1.std::string::~string();           // destroy dm1,
18       Base::~Base();                        // destroy base class part, and
19       throw;                                // propagate the exception
20     }
21    }

inline函数改变后,所有用到该函数的程序都要重新编译,而non-inline函数则只需重新连接就好。

大部分调试器对inline函数调试束手无策。

 

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

Person的文件和它的头文件之间建立了编译依赖关系。如果任一个辅助类(即string, Date,Address和Country)改变了它的实现,或任一个辅助类所依赖的类改变了实现,包含Person类的文件以及任何使用了Person类的文件就必须重新编译。

解决方法: 用前置声明替换#include头文件, 声明的类型只能被使用为引用或指针,或在函数的声明中。
01    //方案一: handle class,pimpl idiom
02    //person.h
03    #ifndef PERSON_H
04    #define PERSON_H
05    #include <string>                      // 不能前置声明,因为std::string是个basic_string<char>类型的typedef
06    #include <memory>                      // for tr1::shared_ptr; see below
07    class PersonImpl;                      // 前置声明
08    class Date;                            // 前置声明
09    class Address;                         // 前置声明
10    class Person {
11    public:
12     Person(const std::string& name, const Date& birthday,
13            const Address& addr);
14     std::string name() const;
15     std::string birthDate() const;
16     std::string address() const;
17    private:                                   // ptr to implementation;
18        std::tr1::shared_ptr<personimpl> pImpl;  // see Item 13 for info on
19    };                                         // std::tr1::shared_ptr
20    #endif
21     
22    //person.cpp
23    #include "Person.h"          // we're implementing the Person class,
24    #include "PersonImpl.h"      // we must also #include PersonImpl's class
25    Person::Person(const std::string& name, const Date& birthday,
26                   const Address& addr)
27    : pImpl(new PersonImpl(name, birthday, addr)) {
28    }
29    std::string Person::name() const {
30      return pImpl->name();
31    }
32    std::string Person::birthDate() const {
33        return pImpl->birthDate();
34    }
35    //PersonImpl.h
36    #ifndef PERSONIMPL_H
37    #define PERSONIMPL_H
38    #include "Date.h"
39    #include "Address.h"
40    class PersonImpl {
41    public:
42         PersonImpl(const std::string& name, const Date& birthday,
43              const Address& addr):theName(name),theBirthDate(birthday),theAddress(addr){}
44         std::string name() const { return theName; }
45         std::string birthDate()const  { return theBirthDate.toString();}
46    private:
47             std::string theName;        // implementation detail
48             Date theBirthDate;          // implementation detail
49             Address theAddress;         // implementation detail
50    };
51    #endif
52     
53    //Date.h
54    #ifndef DATE_H
55    #define DATE_H
56    class Date {
57    public:
58        Date(int m, int d, int y):month(m),day(d),year(y){
59            }
60        std::string toString() const{ char buff[20];sprintf(buff,"today is %d %d %d",month,day,year);return buff;}
61    private:
62        int month;
63        int day;
64        int year;
65    };
66    #endif
67    </personimpl></memory></char></string>

设想下如果为Date.h被修改,包含它的PersonImpl.h就需要重新编译,person.cpp要重新编译,而person.h不变,所以外部程序包含person.h不需要重新编译。

而如果PersonImpl增加删减一个成员变量,构造函数就要改变,person.cpp就需要重新编译,如果因此person.h的构造函数及因增减的成员相关的成员函数变化了,即person本身的接口变了,那么包含person.h的所有文件都要重新编译。但是需要使用Person类的类也可以在改类头文件中使用前置声明, 然后只需重新编译该类,而包含该类头文件的其他类就不需要重新编译了。

所以本条款就是把编译相关的文件局部于一定的的文件层次中。

 

另外可以为声明式和定义式提供不同的头文件: 文件需要保持一致性,如<iosfwd>包含iostream的各组件的声明式,其定义则分布在<sstream><streambuf><fstream><iostream>

 
01    //方案二: Interface class
02    //Person.h
03    #ifndef PERSON_H
04    #define PERSON_H
05    #include <string>                      // standard library components
06                                           // shouldn't be forward-declared
07    #include <memory>                      // for tr1::shared_ptr; see below
08     
09    class Date;                            // forward decls of classes used in
10    class Address;                         // Person interface
11    class Person {
12    public:
13      static std::tr1::shared_ptr<person>    // return a tr1::shared_ptr to a new
14       create(const std::string& name,      // Person initialized with the
15              const Date& birthday);         // why a tr1::shared_ptr is returned
16       virtual std::string name() const = 0;
17       virtual std::string birthDate() const = 0;
18    };                                     
19    #endif
20     
21    //Person.cpp
22    #include "Person.h"     
23    #include "RealPerson.h"
24     
25    std::tr1::shared_ptr<person> 
26       Person::create(const std::string& name,    
27              const Date& birthday){
28                  return std::tr1::shared_ptr<person>(new RealPerson(name, birthday));
29    }
30     
31    //RealPerson.h
32    #ifndef REALPERSON_H
33    #define REALPERSON_H
34    #include "Date.h"
35    class Address;
36    class RealPerson: public Person {
37    public:
38      RealPerson(const std::string& name, const Date& birthday)
39      : theName(name), theBirthDate(birthday)
40      {}
41      virtual ~RealPerson() {}
42      std::string name() const {return theName;}
43      std::string birthDate() const{return theBirthDate.toString();}
44    private:
45      std::string theName;
46      Date theBirthDate;
47    };
48    #endif
49    </person></person></person></memory></string>

 

因为Person是抽象类不能创建对象实例,而在现实中,Person::create可能会创建不同的类型的derived class对象,取决于额外参数值, 读自文件或数据库的数据,环境变量等。

 

而无论handle class还是 interface class 都必然会使你在运行期付出额外的空间和时间代价。


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

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

 
01    class Bird {
02    public:
03      virtual void fly();                  // birds can fly
04    };
05     
06    class Penguin:public Bird {            // penguins are birds
07    };// 企鹅会飞吗???
08     
09    /*********************************************************/
10    class Bird {
11     
12      ...                  // no fly function is declared
13     
14    };
15    class FlyingBird: public Bird {
16    public:
17      virtual void fly();
18    };
19    class Penguin: public Bird {
20     
21      ...                  // no fly function is declared
22    };
23     
24    /*********************************************************/
25    void error(const std::string& msg);       // defined elsewhere
26    class Penguin: public Bird {
27    public:
28      virtual void fly() { error("Attempt to make a penguin fly!");}
29    }; //不是说企鹅不会飞,而其实是在说企鹅会飞,但尝试那么做是一种错误。。

当然如果设计的类无关乎会不会飞,就最好了。。。

 
01    class Rectangle {
02    public:
03      virtual void setHeight(int newHeight);
04      virtual void setWidth(int newWidth);
05      virtual int height() const;               // return current values
06      virtual int width() const;
07    };
08    void makeBigger(Rectangle& r)    //增加矩形面积,高不变,宽增加
09    {
10      int oldHeight = r.height();
11      r.setWidth(r.width() + 10);             
12      assert(r.height() == oldHeight);       
13    }                                         
14    class Square: public Rectangle {...};
15    Square s;
16    assert(s.width() == s.height());           // this must be true for all squares
17    makeBigger(s);                          //正方形能高不变只增加宽吗,这样还是正方形吗????
18    assert(s.width() == s.height());           // this must still be true for all squares

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

    derived classes内的名称会遮掩base classes内的名称。在public继承下从来没人希望如此
    为了让被遮掩的名称再见天日,可使用using声明式或转交函数

01    class Base {
02    private:
03      int x;
04    public:
05      virtual void mf1() = 0;
06      virtual void mf1(int);
07      virtual void mf2();
08      void mf3();
09      void mf3(double);
10    };
11    class Derived: public Base {
12    public:
13      virtual void mf1();
14      void mf3();
15      void mf4();
16    };
17    Derived d;
18    int x;
19    d.mf1();                   // fine, calls Derived::mf1
20    d.mf1(x);                  // error! Derived::mf1 hides Base::mf1
21    d.mf2();                   // fine, calls Base::mf2
22    d.mf3();                   // fine, calls Derived::mf3
23    d.mf3(x);                  // error! Derived::mf3 hides Base::mf3
24    //遮掩名称,和类型无关
25     
26    void Derived::mf4(){
27        mf2();}
28    //查找顺序local->Derived->Base->Base 的namespace-> global

可以在Derived class内用using声明被遮掩的Base class名称。
1    class Derived: public Base {
2    public:
3      using Base::mf1;// 让Base class内名为mf1的所有东西都在Derived作用域内可见
4      virtual void mf1();
5      void mf3();
6      void mf4();
7    };

如果不想继承base class所有函数,只让部分函数可见

转交函数forwaring function

01    class Base {
02    public:
03      virtual void mf1() = 0;
04      virtual void mf1(int);
05    };
06    class Derived: private Base { //因为public继承意味is-a关系,不能遮掩Base
07    public:
08      virtual void mf1() { Base::mf1(); } //转交函数, 隐式inline
09    };
10    Derived d;
11    int x;
12    d.mf1();             // fine, calls Derived::mf1
13    d.mf1(x);            // error! Base::mf1() is hidden

 

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

    成员函数的接口总是会被继承。public继承意味is-a,所以某个函数可以用于base class身上,也一定可以用于它的derived classes。
    声明一个pure virtual函数的目的是为了让derived classes只继承函数接口
    声明impure virtual(非纯虚)函数的目的,是为了让derived classes继承该函数的接口和缺省实现。
    声明non-virtual函数的目的是为了让derived classes继承函数的接口及一份强制性实现。

 
01    //关于impure virtual的缺省实现
02    class Airport { ... };
03    class Airplane {
04    public:
05      virtual void fly(const Airport& destination);
06    };
07    void Airplane::fly(const Airport& destination) {
08      缺省代码,将飞机飞往指定的目的地
09    }
10    class ModelA: public Airplane { ... }; ///继承Airplane::fly缺省行为
11    class ModelB: public Airplane { ... };///继承Airplane::fly缺省行为
12    class ModelC: public Airplane {
13    //未声明fly函数
14    };
15    Airport PDX(...);
16    Airplane *pa = new ModelC;
17    pa->fly(PDX);           // calls Airplane::fly!

切断vitural函数接口和其缺省实现的连接
01    class Airplane {
02    public:
03      virtual void fly(const Airport& destination) = 0; //声明为纯虚函数
04    protected:
05      void defaultFly(const Airport& destination);
06    };
07    void Airplane::defaultFly(const Airport& destination) {
08      缺省行为,将飞机飞至指定的目的地
09    }
10    class ModelA: public Airplane {
11    public:
12      virtual void fly(const Airport& destination){
13            defaultFly(destination); }
14    };
15    class ModelB: public Airplane {
16    public:
17      virtual void fly(const Airport& destination){
18            defaultFly(destination); }
19    };
20    class ModelC: public Airplane {
21    public:
22      virtual void fly(const Airport& destination); //Airplane中的纯虚函数迫使必须提供自己的fly版本
23    };
24    void ModelC::fly(const Airport& destination) {
25      将C型飞机飞至指定的目的地
26     
27    }

有人担心fly和defaultFly过度雷同的名称而引起的class命名空间污染问题。。
01    class Airplane {
02    public:
03      virtual void fly(const Airport& destination) = 0;//声明为纯虚函数,让derived classes必须重写
04    };
05    void Airplane::fly(const Airport& destination) { //提供缺省行为,只能被derived classes显示调用
06      缺省行为                                        //而原先defaultFly是protected,现在为public
07    }
08    class ModelA: public Airplane {
09    public:
10      virtual void fly(const Airport& destination) {
11             Airplane::fly(destination); }
12    };
13    class ModelB: public Airplane {
14    public:
15      virtual void fly(const Airport& destination){
16             Airplane::fly(destination); }
17    };
18    class ModelC: public Airplane {
19    public:
20      virtual void fly(const Airport& destination);
21    };
22    void ModelC::fly(const Airport& destination) {
23      自定义行为
24    }

non-virtual函数: 不变性invariant凌驾其特异性specialization

 

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

    借由Non-Virtual Interface手法实现Template Method设计模式

01    //NVI手法:增加一个外层函数,里面调用虚函数,可增加事前事后处理工作。其实还是使用virtual
02    class GameCharacter {
03    public:
04      int healthValue() const {             //增加一层外覆器wrapper                      
05        ...                                 //做一些事前工作,设定场景
06        int retVal = doHealthValue();       // do the real work
07        ...                                 //事后清理
08        return retVal;
09      }
10    private:
11      virtual int doHealthValue() const {
12      ...
13      }                         
14    };

NVI手法其实没有必要让virtual一定是private,可以为protected(如derived class调用base class里同名函数),可以为public(如virtual析构函数)

    借由Function Pointers 实现Strategy设计模式:

         优点:每个对象可以有自己的健康计算函数和可在运行期改变计算函数; 缺点:可能降低封装性 (不能访问non-public成员),需要声明friend或提供public访问接口
01    class GameCharacter;                               // forward declaration
02    int defaultHealthCalc(const GameCharacter& gc);
03    class GameCharacter {
04    public:
05      typedef int (*HealthCalcFunc)(const GameCharacter&);
06      explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
07          :healthFunc(hcf){}
08      int healthValue() const { return healthFunc(*this); }
09    private:
10      HealthCalcFunc healthFunc;
11    };
12    class EvilBadGuy: public GameCharacter { //同一人物的不同实体可以有不同计算函数,比起virtual函数
13    public:
14      explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)
15          :GameCharacter(hcf) {
16             ... }
17    };
18    int loseHealthQuickly(const GameCharacter&);    // health calculation
19    int loseHealthSlowly(const GameCharacter&);     // funcs with different
20     
21    EvilBadGuy ebg1(loseHealthQuickly);
22    EvilBadGuy ebg2(loseHealthSlowly);

    借由tr1::function完成Strategy设计模式

01    class GameCharacter;                                 // as before
02    int defaultHealthCalc(const GameCharacter& gc);      // as before
03    class GameCharacter {
04    public:
05       //HealthCalcFunc为任何可调用物,如函数指针、函数对象;接受可隐式转为const GameCharacter&的形参,返回兼容int的类型
06       typedef std::tr1::function<int (const="" gamecharacter&)=""> HealthCalcFunc;
07       explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
08       : healthFunc(hcf){}
09       int healthValue() const { return healthFunc(*this);}
10    private:
11      HealthCalcFunc healthFunc;
12    };
13    short calcHealth(const GameCharacter&);    
14    struct HealthCalculator {                      
15      int operator()(const GameCharacter&) const  
16      { ... }                                 
17    };
18    class GameLevel {
19    public:
20      float health(const GameCharacter&) const;
21    };                                           
22    class EvilBadGuy: public GameCharacter {
23      ...
24    };
25    class EyeCandyCharacter:   public GameCharacter {
26      ...                                            
27    };
28     
29    EvilBadGuy ebg1(calcHealth);                  
30    EyeCandyCharacter ecc1(HealthCalculator());  //使用函数对象
31    GameLevel currentLevel;                    
32    EvilBadGuy ebg2(                             //使用某个类的成员函数   
33      std::tr1::bind(&GameLevel::health,
34              currentLevel, 
35              _1)
36    );
37    //GameLevel::health实际上有两个形参,一个为隐含this指针,-1为占位符,为bind返回的函数对象调用时候的第几个形参
38    </int>

 

    古典的Strategy设计模式 : 将计算函数做成一个分离的继承体系中的virtual成员函数

01    class GameCharacter;
02    class HealthCalcFunc {
03    public:
04      virtual int calc(const GameCharacter& gc) const
05      { ... }
06    };
07    HealthCalcFunc defaultHealthCalc;
08    class GameCharacter {
09    public:
10      explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
11      : pHealthCalc(phcf)
12      {}
13      int healthValue() const
14      { return pHealthCalc->calc(*this);}
15    private:
16      HealthCalcFunc *pHealthCalc;
17    };
18    //可为HealthCalcFunc继承体系添加derived class来增加健康计算方法

 

条款36: 绝不重新定义继承而来的non-virtual函数 : public 继承is-a关系,non-virtual不变性凌驾特异性条款34

条款37: 绝不重新定义继承而来的缺省参数值
01    //virtual是动态绑定,默认参数是静态绑定,所以看静态类型
02    class Shape {
03    public:
04      enum ShapeColor { Red, Green, Blue };
05      virtual void draw(ShapeColor color = Red) const = 0;
06    };
07    class Rectangle: public Shape {
08    public:
09      virtual void draw(ShapeColor color = Green) const;
10    };
11    class Circle: public Shape {
12    public:
13      virtual void draw(ShapeColor color) const; //动态绑定下才会从base继承缺省参数值
14    };
15     
16    Shape *pr = new Rectangle;       // static type = Shape*
17    pr->draw();                      // calls Rectangle::draw(Shape::Red)!!!!

 

解决方案: NVI手法,条款35
01    class Shape {
02    public:
03      enum ShapeColor { Red, Green, Blue };
04      void draw(ShapeColor color = Red) const          {
05        doDraw(color);                                
06      }
07    private:
08      virtual void doDraw(ShapeColor color) const = 0;  // the actual work is
09    };                                                  // done in this func
10     
11    class Rectangle: public Shape {
12    public:
13    private:
14      virtual void doDraw(ShapeColor color) const;  //无须指定缺省参数值
15    };

 

条款38: 通过复合塑模出has-a或“根据某物实现出”。Model “has-a” or ”is-implemented-in-terms-of”through composition.

当复合(composition)发生于应用域内的对象之间,表现出has-a关系;当发生于实现域内则是表现出is-impemented-in-terms-of关系。

复合即为包含。应用域:人、汽车等实物。 实现域:缓冲区、互斥器、查找树等等。

 

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

    private继承意味is-implemented-in-terms-of.它通常比复合的级别低. 但是当derived class需要访问protected base class成员,或需要重新定义继承而来的virtual函数时,才合理。
    和复合不同,private继承可以造成empty base最优化

01    class Person { ... };
02    class Student: private Person { ... };     // inheritance is now private
03    void eat(const Person& p);                 // anyone can eat
04    void study(const Student& s);              // only students study
05     
06    Person p;                                  // p is a Person
07    Student s;                                 // s is a Student
08    eat(p);                                    // fine, p is a Person
09    eat(s);                                    // error! a Student isn't a Person
10    //private继承,编译器不会自动将一个derived class 对象转换为一个base class对象

private继承意味is-implemented-in-terms-of, 与条款38的复合在应用域的表现一样。

尽可能使用复合, 必要时才使用private继承。合适才是必要?当protected成员或virtual函数牵扯进来时。还有一种激进情况,当空间方面的厉害关系足以踢翻private继承的支柱时。

 

设计一个Widget类,通过复用Timer类来周期性审查每个成员函数的被调用次数。并重写Timer的虚函数。
01    class Timer {
02    public:
03      explicit Timer(int tickFrequency);
04      virtual void onTick() const;          // automatically called for each tick
05      ...
06    };
07    //为了让Widget重新定义Timer的virtual,必须继承Timer
08    //但public继承在此并不适用,is-a关系,而且Widget调用OnTick容易造成接口误用,违反条款18
09     
10    //所以private继承
11    class Widget: private Timer {
12    private:
13      virtual void onTick() const;           // look at Widget usage data, etc.
14      ...
15    };
16     
17    //但private继承绝非必要,可以使用public继承加复合
18    class Widget {
19    private:
20      class WidgetTimer: public Timer {
21      public:
22        virtual void onTick() const;
23        ...
24      };
25       WidgetTimer timer;
26      ...
27    };

为什么愿意选择public继承加复合而非private继承?

1)如果你想从Widget派生出其他类,而同时想阻止其derived class重新定义OnTick,但无论什么继承派生类都可以重写虚函数。

  但WidgetTimer是private成员,Widget的derived class将无法取用WidgetTimer,因此无法继承它或重新定义它的virtual函数

2)你可能会想要将Widget的编译依存性降至最低。如果Widget继承Timer,当Widget编译时,Timer定义必须可见。

   如果WidgetTimer移到Widget之外而Widget内含指针指向一个WidgetTimer,则不需要include。

激进情况: 如果你的class不到任何数据,没有non-static成员变量,没有virtual函数(会有vptr),没有virtual base class。而C++裁定凡是独立对象都必须有非零大小。
01    class Empty {};
02    class HoldsAnInt {
03    private:
04      int x;
05      Empty e;
06    };
07    //  sizeof(HoldsAnInt) > sizeof(int);
08    //C++官方勒令安插一个char到空对象。然而齐位需求alignment,可能被扩充到int
09     
10    class HoldsAnInt: private Empty {
11    private:
12      int x;
13    };
14    //sizeof(HoldsAnInt) == sizeof(int)
15    //EBO(empty base optimization)空白基类最优化,EBO一般只在单一继承下才可行

 

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

    多重继承比单一继承复杂。可能导致新的歧义性,以及对virtual继承的需要
    virtual继承会增加大小、速度、初始化(赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况。
    多重继承的确有正当用途。public继承某个Interface class,private继承某个协助实现的class

1    //多重继承,virtual base class
2    class File { ... };
3    class InputFile: virtual public File { ... };
4    class OutputFile: virtual public File { ... };
5    class IOFile: public InputFile,
6                  public OutputFile
7    { ... };

virtual继承的classes所产生对象往往体积更大,访问virtual base classes 的成员变量时速度更慢。

而且virtual bsae class的初始化责任是由继承体系中的最底层class负责的。

因此:1)非必要不要使用virtual继承    2)如必须使用virtual  base classes,尽可能避免在其中放置数据。

 

现在要以IPerson的指针和引用来编写程序,并用factory function创建对象。

现在要写个CPerson类,继承并重写IPerson的pure virtual函数的实现。

我们可以自己写这个实现,但我们已经找到一个PersonInfo可以提供CPerson所需要的实现内容。

is-implemented-in-terms有两种方式,复用和private继承,但本例CPerson要重新定义valueDelimOpen和valueDelimClose,所以这里用private继承,当然也可以复合+继承。
01    //public继承某接口,private继承自某实现
02     
03    class IPerson {                         
04    public:                                 
05      virtual ~IPerson();
06      virtual std::string name() const = 0;
07      virtual std::string birthDate() const = 0;
08    };
09    class DatabaseID { ... };               
10    class PersonInfo {                     
11    public:                               
12      explicit PersonInfo(DatabaseID pid);   
13      virtual ~PersonInfo();
14      virtual const char * theName() const;
15      virtual const char * theBirthDate() const;
16      virtual const char * valueDelimOpen() const;
17      virtual const char * valueDelimClose() const;
18    };
19     
20    class CPerson: public IPerson, private PersonInfo {     // note use of MI
21    public:
22      explicit CPerson(    DatabaseID pid): PersonInfo(pid) {}
23      virtual std::string name() const                      // implementations
24      { return PersonInfo::theName(); }                     // of the required IPerson member
25      virtual std::string birthDate() const                 // functions
26      { return PersonInfo::theBirthDate(); }
27    private:                                                // redefinitions of
28      const char * valueDelimOpen() const { return ""; }    // inherited virtual
29      const char * valueDelimClose() const { return ""; }   // delimiter
30    };                                                      // functions

【转载】http://www.cnblogs.com/Atela/archive/2011/04/14/2016420.html