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

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

____________________________________________________________________________________________________________________________________

条款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&);    //健康函数计算函数1
int loseHealthSlowly(const GameCharacter&);        //健康函数计算函数2

EvilBadGuy 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-int
struct 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 函数---你唯一应该覆写的东西---却是动态绑定。
//一个用以描述几何形状的class
class 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-of
class 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 ”界限函数“

};

____________________________________________________________________________________________________________________________________

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值