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

游戏中的人物伤害值计算问题。

(一)方法(1):一般来讲可以使用虚函数的方法:

class GameCharacter {
    public:
        virtual int healthValue() const;    //返回人物的体力值,派生类可以做出修改
        ...
};  

这确实是一个显而易见的设计选择。但因为这样的设计过于显而易见,可能不会对其它可选方法给予足够的关注。我们来考虑一些处理这个问题的其它方法。

(二)方法(2):使用NVI方法,在基类中使用一个公有的普通函数调用私有的虚函数。

class GameCharacter{
    public:
        int healthValue() const {       //派生类不能重新定义它
            ...                         //做一些事前工作
            int retVal = doHealthValue();       //调用私有函数进行计算
            ...                        //做一些事后工作
            return retVal;
        }
    private:
        virtual int doHealthValue() const{      //派生类可以重新定义
            ...         //提供缺省算法
        }
};  

NVI手法的一个优势通过
"做事前工作" 和 "做事后工作" 两个注释在代码中标示出来。这意味着那个外覆器可以确保在virtual函数被调用前,特定的背景环境被设置,而在调用结束后,这些背景环境被清理。例如,事前工作可以包括锁闭一个mutex,生成一条日志,校验类变量和函数的先决条件是否被满足,等等。事后工作可以包括解锁一个mutex,校验函数的事后条件,再次验证类约束条件,等等。如果你让客户直接调用virtual函),确实没有好的方法能够做到这些。

NVI手法其实没必要让virtual函数一定是private。有时必须是protected(在继承体系中,子类要直接调用基类成员函数)。还有时候甚至是public,这么一来的话就不能实施NVI手法了。

(三)方法(3)使用函数指针。

class GameCharacter;        //前置声明
//以下函数是计算健康指数的缺省算法
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;
};  

这种方法的优点是它能够:

(1)通过定义不同的体力值计算方法,同种类型的人物通过调用不同的函数可以实现不同的计算方法:

class EvilBadGuy: public GameCharacter {
public:
    explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)
        : GameCharacter(hcf)
    {...}
    ...
};
int loseHealthQuickly(const GameCharacter&);
int loseHealthSlowly(const GameCharacter&);

EvilBadGuy ebg1(loseHealthSlowly);//相同类型的人物搭配
EvilBadGuy ebg2(loseHealthQuickly);//不同的健康计算方式

(2)人物的体力计算方法可以在运行期间变更(相当于为GameCharacter的私有变量重新赋值)。

例如GameCharacter可提供一个成员函数setHealthCalculator,用来替换当前的健康指数计算函数。

使用函数指针这种方法(包括以后的两种方法)可能会使用类外的函数,从而降低封装性。所以在用这种方法的时候,他的上面两种优点能否弥补他的缺点(降低类的封装性)是我们在整个设计之前需要考虑的东西。

(四)方法(4)使用tr1::function完成Strategy模式。

class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
    public:
        //HealthCalcFunc可以是任何“可调用物”,可被调用并接受任何兼容于GameCharacter之物,返回任何兼容于int的东西,详下:
        typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
        //这种定义表示HealthCalcFunc作为一种类型,接受GameCharacter类型的引用,并返回整数值,其中支持隐式类型转换
        explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf)
        {}
        int healthValue() const{
            return healthFunc(*this);
        }
        ...
    private:
        HealthCalcFunc healthFunc;
};  

那个签名代表的函数是“接受一个reference指向const GameCharacter,并返回int”

std::tr1::function<int (const GameCharacter&)>

所谓兼容,意思是这个可调用物的参数可被隐式转换为const GameCharacter&,而其返回类型可被隐式转换成int。

在这里,GameCharacter持有一个tr1::function对象,相当于一个指向函数的泛化指针。

在使用这个方法时P175介绍了三种调用方式,即使用三种方式初始化GameCharacter的派生类:一个具体的函数,一个函数对象,以及一个像std::tr1::bind(&GameLevel::health,
currentLevel, _1)这样用一个对象的成员函数。

EvilBadGuy ebg1(calcHealth);        //使用某个函数
EyeCandyCharacter ecc1(HeathCalculator());      //使用某个函数对象(包含一个函数的结构体)
GameLevel currentLevel;
EvilBadGuy ebg2(std::tr1::bind(&GameLevel::health, currentLevel, _1));  

完整代码像这样:

客户在“指定健康计算函数”这件事上有更惊人的弹性:

short calcHealth(const GameCharacter&); //函数return non-int
struct HealthCalculator {//为计算健康而设计的函数对象
    int operator() (const GameCharacter&) const
    {
        ...
    }
};
class GameLevel {
public:
    float health(const GameCharacter&) const;//成员函数,用于计算健康
    ...
};
class EvilBadGuy : public GameCharacter {
    ...
};
class EyeCandyCharacter : public GameCharacter {
    ...
};

EvilBadGuy ebg1(calcHealth);//函数
EyeCandyCharacter ecc1(HealthCalculator());//函数对象
GameLevel currentLevel;
...
EvilBadGuy ebg2(std::tr1::bind(&GameLevel::health, currentLevel, _1));//成员函数

GameLevel::health宣称它接受两个参数,但实际上接受两个参数,因为它也获得一个隐式参数GameLevel,也就是this所指的那个。然而GameCharacter的健康计算函数只接受单一参数:GameCharacter。如果我们使用GameLevel::health作为ebg2的健康计算函数,我们必须以某种方式转换它,使它不再接受两个参数(一个GameCharacter和一个GameLevel),转而接受单一参数(GameCharacter)。于是我们将currentLevel绑定为GameLevel对象,让它在“每次GameLevel::health被调用以计算ebg2的健康”时被使用。那正是tr1::bind的作为。

(五)方法(5)使用古典的Strategy模式

将健康计算函数做成一个分离的继承体系中的virtual成员函数。

class GameCharacter;
class HealthCalcFunc {
    ...
    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;
};

每一个GameCharacter对象都内含一个指针,指向一个来自HealthCalcFunc继承体系的对象。

还可以提供“将一个既有的健康计算算法纳入使用”的可能性--只要为HealthCalcFunc继承体系添加一个derived class即可。

UML图在书上P176。

(六)总结:

虚函数的替代方案有:

(1)使用non-virtual interface(NVI)方法,它是Template Method设计模式的一种特殊形式。使客户通过仅有的非虚函数间接调用私有的虚函数,该公有的非虚函数称为私有虚函数的“外覆器”(wrapper)。公有的非虚函数可以在调用虚函数前后做一些其他工作(如互斥器的锁定与解锁,验证约束条件等)。

(2)将虚函数替换为“函数指针成员变量”,它是Strategy设计模式的一种分解表现形式。

(3)以tr1::function成员变量替换虚函数,从而允许使用任何可调用物搭配一个兼容于需求的签名式(这句话表达太晦涩了,很难理解,例子见下)。它也是Strategy设计模式的某种形式。

(4)将继承体系内的虚函数替换为另一个继承体系内的虚函数,这是Strategy设计模式的传统实现手法。

请记住:

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

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

(3)tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用物(callable entities)。

Effective C++:条款35:考虑virtual函数以外的其他选择,布布扣,bubuko.com

时间: 2024-10-25 12:01:52

Effective C++:条款35:考虑virtual函数以外的其他选择的相关文章

Effective C++ 条款35 考虑virtual函数以外的其他选择

1. 在一个继承层次中,不同的类要实现同一接口的不同实现,最先想到的可能是虚函数,假设一个存在一个继承体系,这个集成体系中的每一层都需要一个名为fun函数,那么可能会像这样实现: clase Base{ public: ... virtual fun(int num){...} private: ... } class Derived:public Base{ public: ... virtual fun(int num){} private: ... } 但除了将fun设为虚函数,还有其他选

Effective C++笔记_条款35 考虑virtual 函数以外的其他选择

因为其他的事情耽误了看书,现在将看的笔记记录下来. 1 class GameCharacter { 2 public: 3 virtual int healthValue() const; 4 }; 1. 藉由Non-Virtual Interface 手法实现 Template Method模式 (1)non-virtual interface(NVI): 令客户通过public non-virtual 成员函数间接调用private virtual函数 (2) virtual 函数的外覆器(

Effective C++ Item 35 考虑 virtual 函数以外的实现

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.virtual 函数版本 class GameCharacter{ public: virtual int healthValue() const; //返回人物的健康指数, derived classes 可重新定义它 }; 2.使用 non-virtual interface 手法,那是 Template Method 设计模式的一种特殊形式. 让客户通过 public non-v

《Effective C++》:条款35:考虑virtual函数以外的其他选择

条款35:考虑virtual函数以外的其他选择 条款35考虑virtual函数以外的其他选择 藉由Non-virtual Interface手法实现Template Method模式 藉由Function Pointers实现Strategy模式 藉由tr1function完成Strategy模式 古典的Strategy模式 摘要 virtual函数在派生中经常用到,在遇到一些问题时用virtual函数没问题,但是有时候我们应该思考一下是否有替代方案,以此来拓宽我们的视野. 假如现在正在写一个游

effective c++ 条款7 declare virtual destructor for polymophyc base class

这似乎很明显. 如果base class的destructor不是virtual,当其derived class作为基类使用,析构的时候derived class的数据成员将不会被销毁. 举个例子 我们有个交通工具的类作为基类, 它的析构函数不是virtual class transportTool { public: ~transportTool(); } 然后有一个汽车类继承它 class auto: public transportTool { public: int numWheels;

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

virtual函数的替代方案包括NVI手法及Strategy设计模式的多种手法.NVI手法自身是一个特殊形式的Template Method设计模式. 将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员. tr1::function对象的行为就想一般函数指针.这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用物(callable entities).

efective C++ 条款35:考虑除virtual函数外的其他选择

1.籍由NVI手法 实现 Template Method 模式 Template Method 模式 对于某一个业务逻辑(算法实现)在不同的对象中有不同的细节实现,但是逻辑(算法)的框架(或通用的应用算法)是相同的.Template Method提供了这种情况的一个实现框架. Template Method模式是采用继承的方式实现这一点:将逻辑(算法)框架放在抽象基类中,并定义好细节的接口,子类中实现细节. 其关键点就是将通用算法封装在抽象基类中 NVI手法中所用的non_virtual函数成为

条款35:考虑virtual函数以外的其他选择(Consider alternative to virtual functions)

NOTE: 1.virtual 函数的替代方案包括NVI手法及Strategy设计模式的多种形式.NVI手法自身是一个特殊形式的Template Method设计模式. 2.将机能从成员函数移到外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员. 3.tr1::function 对象的行为就像一般函数指针.这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用物(callable entiies). 不懂这条,设计模式不懂!!!

More Effective C++ 条款35 让自己习惯于标准C++ 语言

(由于本书出版于1996年,因此当时的新特性现在来说可能已经习以为常,但现在重新了解反而会起到了解C++变迁的作用) 1. 1990年后C++的重要改变 1). 增加了新的语言特性:RTTI,namespaces,bool,关键词mutable和explicit,enums作为重载函数之自变量所引发的类型晋升转换,以及"在class 定义区内直接为整数型(intergral) const static class members设定初值"的能力. 2). 扩充了Templates的特性