Effective C++读书笔记(条款35-40)

来源:互联网 发布:农村淘宝报名 编辑:程序博客网 时间:2024/06/05 21:51

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

____________________________________________________________________________________________________________________________________

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

#1.virual函数的四个替代方案:
(1).使用non-virtual interface(NVI)手法,那是 Template Method 设计模式
的一种特殊形式。它以public non-virtual 成员函数包裹较低访问性(private
或 protected)的virtual函数。
(2).将virtual函数替换为 "函数指针成员变量", 这是Strategy设计模式的一种
分解表现方式。
(3).以tr1::function 成员变量替换 virtual 函数,因而允许使用任何可调用物
(callable entity) 搭配一个兼容于需求的签名式。这也是 Strategy设计模式的
某种形式。
(4).将继承体系内的 virtual 函数替换为另一个继承体系内的virtual 函数。
这是Strategy 设计模式的传统实现手法。

<1>.由Non-Virtual Interface手法实现Template Method模式

//考虑以下代码:class GameCharacter{public:    int healthValue() const             //derived classes不重新定义它    {                                    //见36条款        ...                                //做一些事前工作,详下        int retVal = doHealthValue();     //做真正的工作        ...                                //做一些事后工作    }    ...private:    virtual int doHealthValue() const     //derived classes可重新定义它    {        ...    }};//我们称healthValue函数为virutal函数doHealthValue的外覆器,//它提供了完整实现,而virtual函数则提供了具体实现,//通过NVI手法,我们可以更专注于事件如何被具体完成

<2>.由Function Pointers 实现 Strategy 模式
//考虑以下代码:class GameCharacter;    //前置声明(forward declaration)//以下函数是计算健康指数的缺省算法int defaultHealthCalc(const GameCharacter& gc);class GameCharacter{public:    typedef int (*HealthCalcFunc)(const GameCharacter&);    explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)    : healthFunc(hcf){}    int healthValue() const {return healthFunc(*this);}    ...private:    HealthCalcFunc healthFunc;};//同一人物类型之不同实体可以有不同的健康计算函数,例如:class EvilBadGuy:public GameCharacter{public:    explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)    : GameCharacter(hcf)    {...}    ...};int loseHealthQuickly(const GameCharacter&);    //健康函数计算函数1int loseHealthSlowly(const GameCharacter&);        //健康函数计算函数2EvilBadGuy ebg1(loseHealthQuickly);        //相同类型的人物搭配EvilBadGuy ebg2(loseHealthSlowly);        //不同的健康计算方式//defualtHealthCalc并未访问EvilBadGuy的non-public成分,//若要访问non-public成分,需使其成为friends,因此可能//降低class的封装性。//运用函数指针替换virtual函数,其优点(像是"每个对象//可各自拥有自己的健康计算函数”和“可在运行期改变计算//函数”)是否足以弥补缺点(例如可能降低GameCharacter//的封装性),是你必须根据设计情况而抉择的。

<3>.由tr1::function完成Strategy模式

//考虑以下代码:class GameCharacter;int defaultHealthCalc(const GameCharacter&);class GameCharacter{public:    //HealthCalcFunc可以是任何 “可调用物” (callable entity),    //可被调用并接受任何兼容于 GameCharacter 之物,返回任何    //兼容于 int 的东西。详下。    typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;    explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)    : healthFunc(hcf)    {}    int healthValue() const {return healthFunc(*this);}    ...private:    HealthCalcFunc healthFunc;};//如今的tr1::function相当于一个指向函数的泛化指针,//它在“指定健康计算函数”这件事上具有更惊人的弹性:short calcHealth(const GameCharacter&);            //健康计算函数                                                //注意其返回类型为non-intstruct healthCalculator{                        //为计算健康而设计的函数对象    int operator()(const GameCharacter&) const    {...}};class GameLevel{public:    float health(const GameCharacter&)const;    //成员函数,用以计算健康;    ...                                            //注意其non-int 返回类型};class EvilBadGuy:public GameCharacter{            //同前    ...};class EyeCandyCharacter:public GameCharacter{    //另一个人物类型;    ...                                            //假设其构造函数与};                                                //EvilBadGuy相同EvilBadGuy ebg1(calcHealth);                    //人物1,使用某个                                                //函数计算健康函数                                                EveCandyCharacter ecc1(HealthCalculator());        //人物2,使用某个                                                //函数对象计算健康函数GameLevel currentLevel;...EvilBadGuy ebg2(                                 //函数3,使用某个std::tr1::bind(&GameLevel::health,                //成员函数计算健康函数                currentLevel,                _1,)                            //详见以下);//使用tr1::function的好处是允许使用任何可调用物,//只要其调用形式兼容于签名式

<4>.由古典的 Strategy 模式
//考虑以下代码:class GameCharacter;class HealthCalcFunc{public:    ...    virtual int calc(const GameCharacter& gc) const    { ... }    ...};HealthCalcFunc defaultHealthCalc;class GameCharacter{public:    explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc)    : pHealthCalc(phcf)    {}    int healthValue() const    { return pHealthCalc->calc(*this); }    ...private:    HealthCalcFunc* pHealthCalc;};//这个解法的吸引力在于,它提供“将一个既有的健康算法纳入//使用”的可能性---只要为HealthCalcFunc 继承体系添加一个//derived class 即可。
____________________________________________________________________________________________________________________________________
条款36:绝不重新定义继承而来的non-virtual函数
#1.绝不重新定义继承而来的non-virtual函数
理由(1):non-virtual及重新定义下的函数都是静态绑定,
调用它们的结果不是我们所预期的。
class B{public:    void mf();    ...};class D:public B {public:    void mf();    ...};D x;B* pB = &x;D* pD = &x;pB->mf();        //调用B::mf()pD->mf();        //调用D::mf()//对于调用同一个对象x,却出现了不同行为,//这不是我们所希望的,与其如此重新定义,//还不如让其成为virtual函数
理由(2).继承下来的函数分为virtual和non-virtual, virtual意味着
其实现容易被替换,因此实现动态绑定,而non-virtual意味着其不变性
凌驾于特异性,因继承其实现所以应用于静态绑定。
____________________________________________________________________________________________________________________________________
条款37:绝不重新定义继承而来的缺省参数值
#1.绝不重新定义继承而来的缺省参数值,因为缺省参数值都是静态绑定,
而virtual 函数---你唯一应该覆写的东西---却是动态绑定。
//一个用以描述几何形状的classclass Shape{public:    enum ShapeColor { Red, Green, Blue };    //所有形状都必须提供一个函数,用来描绘自己    virtual void draw(ShapeColor color = Red) const = 0;    ...};class Rectangle:public Shape{public:    //注意,赋予不同的缺省参数值。真实糟糕!    virtual void draw(ShapeColor color = Green) const;    ...};class Circle:public Shape{public:    virtual void draw(ShapeColor color) const    //请注意,以上这么写则当客户以对象调用此函数,一定要指定参数值。    //因为静态绑定下这个函数并不从其base 继承缺省参数值。    //但若以指针(或reference) 调用此函数,可以不指定其参数值,    //因为动态绑定下这个函数会从其base继承缺省参数值    ...};//现考虑以下指针:Shape* ps;                        //静态类型为Shape*Shape* pc = new Circle;            //静态类型为Shape*Shape* pr = new Rectangle;        //静态类型为Shape*pc->draw(Shape::Red);            //调用Circle::draw(Shape::Red)pr->draw(Shape::Red);            //调用Rectangle::draw(Shape::Red)//此代码的virtual函数是动态绑定,而缺省参数值却是静态绑定//这造成了一种奇怪的调用方式,不能统一调用,而编译器之所以//不为缺省参数值动态绑定的原因是运行期效率。//那么我们是否可以为derived指定同样的缺省值呢?,就像这样:class Rectangle:public Shape{public:    virtual void draw(ShapeColor color = Red) const;    ...};//答案是否,理由很简单,这造成了我们的代码重复,//而且带有相依性,要是哪天修改了缺省值就要动辄牵动全身了。//一种更好的做法是让non-virtual指定缺省值来代替工作:class Shape{public:    enum draw(ShapeColor color = Red) const     //如今它是一个non-virtual    {        doDraw(color);    //调用一个virtual    }    ...private:    virtual void doDraw(ShapeColor color)const = 0; //真正的工作在此处完成};class Rectangle:public Shape{public:    ...private:    virtual void doDraw(ShapeColor color)const; //无须指定缺省值    ...};//由于non-virtual函数绝不被derived class覆写,这个设计很清楚地使得//draw 函数的color 缺省参数值总是为 true.
____________________________________________________________________________________________________________________________________
条款38:通过复合塑模出 has-a 或 “根据某物实现出”
#1.复合有双层含义,在应用域(application domain), 复合意味 has-a
(有一个)。在实现域(implementation domain), 复合意味
is-implemented-in-terms-of (根据某物实现出)。
____________________________________________________________________________________________________________________________________
条款39:明智而审慎地使用private继承
#1.Private 继承意味 is-implemented-terms-of(根据某物实现出)。它
通常比复合(composition)的级别低。但是当 derived class需要访问
protected base class 的成员,或需要重新定义继承而来的 virtual 函数
时,这么设计是合理的。
//例如我们要使用Timer中的功能,我们可以implemented-in-terms-ofclass widget:private Timer{private:                         //private权限:避免客户调用    virtual void onTick() const; //重新定义我们所需的onTick函数功能    ...};

#2.一种替代private继承的设计是使用复合class,其用法如下:
class Widget{private:    class WidgetTimer:public Timer{    public:        virtual void onTick() const;        ...    };    WidgetTimer timer;    ...};//这种设计的好处是://(1).它可以拥有自己的derived class.//(2).将编译依存性降至最低(分离,相依于声明式)

#3.独立非附属对象的大小一定不为零,但作为附属对象,它存在
EBO(Empty base optimization),即空白基类最优化,可以使其
大小为零。
例如:
class Empty{};class HoldsAnInt:private Empty{private:    int x;}//几乎可以确定,sizeof(HoldsAnInt) = sizeof(int)

#4.和复合(compresition)不同,private继承可以造成empty base最优化。
这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要,此时
#2的替代设计就不能再满足了。
____________________________________________________________________________________________________________________________________
条款40:明智而审慎地使用多重继承
#1.virtual 继承会增加大小,速度,初始化等成本,因此非必要时不要使用
virtual继承。同时它也意味着,virtual base class 数据越少,其使用
价值越高,因此应尽可能避免在其中放置数据。

#2.当涉及继承组合时,多重继承便发挥了其正当用途。例如"public继承某个
Interface class" 和 “private 继承某个协助实现的 class" 的两两组合:
class IPerson{            //该class指出需实现接口public:    virtual ~IPerson();    virtual std::string name() const = 0;    virtual std::string birthDate() const = 0;    virtual std::string birthDate() const = 0;};class DatebaseID{...};  //稍后被使用,细节不重要。class PersonInfo{        //这个class有若干有用函数public:                    //可用以实现IPerson接口。    explicit PersonInfo(DatabaseID pid);    virtual ~PersonInfo(();    virtual const char* theName() const;    virtual const char* theBirthDate() const;    virtual const char* valueDelimOpen() const;    virtual const char* valuedelimClose() const;    ...};class CPerson:public IPerson, private PersonInfo{ //注意,多重继承public:    explicit CPerson(DatabaseID pid):PersonInfo(pid){}    virtual std::string name() const                //实现必要的IPerson函数    {return PersonInfo::theName();}    virtual std::string birthDate() const             //实现必要的IPerson函数    {return PersonInfo::theBirthDate();}private:    const char* valueDelimOpen() const {return "";}    //重新定义继承而来的    const char* valueDelimClose() const {return "";}//virtual ”界限函数“};

____________________________________________________________________________________________________________________________________

1 0
原创粉丝点击