分享
 
 
 

翻译:Effective C++, 3rd Edition, Item 35: 考虑可选的 virtual functions(虚拟函数)的替代方法(下)

王朝c/c++·作者佚名  2006-01-10
窄屏简体版  字體: |||超大  

(点击此处,接上篇)

The Strategy Pattern via tr1::function(经由 tr1::function 实现的策略模式)

一旦你习惯了 templates(模板)和 implicit interfaces(隐式接口)(参见 Item 41)的应用,function-pointer-based(基于函数指针)的方法看上去就有些死板了。健康值的计算为什么必须是一个 function(函数),而不能是某种简单的行为类似 function(函数)的东西(例如,一个 function object(函数对象))?如果它必须是一个 function(函数),为什么不能是一个 member function(成员函数)?为什么它必须返回一个 int,而不是某种能够转型为 int 的类型?

如果我们用一个 tr1::function 类型的对象代替一个 function pointer(函数指针)(诸如 healthFunc),这些约束就会消失。就像 Item 54 中的解释,这样的对象可以持有 any callable entity(任何可调用实体)(例如,function pointer(函数指针),function object(函数对象),或 member function pointer(成员函数指针)),这些实体的标志性特征就是兼容于它所期待的东西。我们马上就会看到这样的设计,这次使用了 tr1::function:

class GameCharacter; // as before

int defaultHealthCalc(const GameCharacter& gc); // as before

class GameCharacter {

public:

// HealthCalcFunc is any callable entity that can be called with

// anything compatible with a GameCharacter and that returns anything

// compatible with an int; see below for details

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

explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)

: healthFunc(hcf)

{}

int healthValue() const

{ return healthFunc(*this); }

...

private:

HealthCalcFunc healthFunc;

};

就像你看到的,HealthCalcFunc 是一个 tr1::function instantiation(实例化)的 typedef。这意味着它的行为类似一个普通的 function pointer(函数指针)类型。我们近距离看看 HealthCalcFunc 究竟是一个什么东西的 typedef:

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

这里我突出了这个 tr1::function instantiation(实例化)的“target signature(目标识别特征)”。这个 target signature(目标识别特征)是“取得一个引向 const GameCharacter 的 reference(引用),并返回一个 int 的函数”。这个 tr1::function 类型的(例如,HealthCalcFunc 类型的)对象可以持有兼容于这个 target signature(目标识别特征)的 any callable entity(任何可调用实体)。兼容意味着这个实体的参数能够隐式地转型为一个 const GameCharacter&,而它的返回类型能够隐式地转型为一个 int。

与我们看到的最近一个设计(在那里 GameCharacter 持有一个指向一个函数的指针)相比,这个设计几乎相同。仅有的区别是目前的 GameCharacter 持有一个 tr1::function 对象——指向一个函数的 generalized(泛型化)指针。除了达到“clients(客户)在指定健康值计算函数时有更大的灵活性”的效果之外,这个变化是如此之小,以至于我宁愿对它视而不见:

short calcHealth(const GameCharacter&); // health calculation

// function; note

// non-int return type

struct HealthCalculator { // class for health

int operator()(const GameCharacter&) const // calculation function

{ ... } // objects

};

class GameLevel {

public:

float health(const GameCharacter&) const; // health calculation

... // mem function; note

}; // non-int return type

class EvilBadGuy: public GameCharacter { // as before

...

};

class EyeCandyCharacter: public GameCharacter { // another character

... // type; assume same

}; // constructor as

// EvilBadGuy

EvilBadGuy ebg1(calcHealth); // character using a

// health calculation

// function

EyeCandyCharacter ecc1(HealthCalculator()); // character using a

// health calculation

// function object

GameLevel currentLevel;

...

EvilBadGuy ebg2( // character using a

std::tr1::bind(&GameLevel::health, // health calculation

currentLevel, // member function;

_1) // see below for details

);

就个人感觉而言:我发现 tr1::function 能让你做的事情是如此让人惊喜,它令我浑身兴奋异常。如果你没有感到兴奋,那可能是因为你正目不转睛地盯着 ebg2 的定义并对 tr1::bind 的调用会发生什么迷惑不解。请耐心地听我解释。

比方说我们要计算 ebg2 的健康等级,应该使用 GameLevel class(类)中的 health member function(成员函数)。现在,GameLevel::health 是一个被声明为取得一个参数(一个引向 GameCharacter 的引用)的函数,但是它实际上取得了两个参数,因为它同时得到一个隐式的 GameLevel 参数——指向 this。然而,GameCharacters 的健康值计算函数只取得单一的参数:将被计算健康值的 GameCharacter。如果我们要使用 GameLevel::health 计算 ebg2 的健康值,我们必须以某种方式“改造”它,以使它适应只取得唯一的参数(一个 GameCharacter),而不是两个(一个 GameCharacter 和一个 GameLevel)。在本例中,我们总是要使用 currentLevel 作为 GameLevel 对象来计算 ebg2 的健康值,所以每次调用 GameLevel::health 计算 ebg2 的健康值时,我们就要 "bind"(凝固)currentLevel 来作为 GameLevel 的对象来使用。这就是 tr1::bind 的调用所做的事情:它指定 ebg2 的健康值计算函数应该总是使用 currentLevel 作为 GameLevel 对象。

我们跳过一大堆的细节,诸如为什么 "_1" 意味着“当为了 ebg2 调用 GameLevel::health 时使用 currentLevel 作为 GameLevel 对象”。这样的细节并没有什么启发性,而且它们将转移我所关注的基本点:在计算一个角色的健康值时,通过使用 tr1::function 代替一个 function pointer(函数指针),我们将允许客户使用 any compatible callable entity(任何兼容的可调用实体)。很酷是不是?

The "Classic" Strategy Pattern(“经典的”策略模式)

如果你比 C++ 更加深入地进入 design patterns(设计模式),一个 Strategy 的更加习以为常的做法是将 health-calculation function(健康值计算函数)做成一个独立的 health-calculation hierarchy(健康值计算继承体系)的 virtual member function(虚拟成员函数)。做成的 hierarchy(继承体系)设计看起来就像这样:

如果你不熟悉 UML 记法,这不过是在表示当把 EvilBadGuy 和 EyeCandyCharacter 作为 derived classes(派生类)时,GameCharacter 是这个 inheritance hierarchy(继承体系)的根;HealthCalcFunc 是另一个带有 derived classes(派生类)SlowHealthLoser 和 FastHealthLoser 的 inheritance hierarchy(继承体系)的根;而每一个 GameCharacter 类型的对象包含一个指向“从 HealthCalcFunc 派生的对象”的指针。

这就是相应的框架代码:

class GameCharacter; // forward declaration

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;

};

这个方法的吸引力在于对于熟悉“标准的”Strategy pattern(策略模式)实现的人可以很快地识别出来,再加上它提供了通过在 HealthCalcFunc hierarchy(继承体系)中增加一个 derived class(派生类)而微调已存在的健康值计算算法的可能性。

Summary(概要)

这个 Item 的基本建议是当你为尝试解决的问题寻求一个设计时,你应该考虑可选的 virtual functions(虚拟函数)的替代方法。以下是对我们考察过的可选方法的一个简略的回顾:

使用 non-virtual interface idiom (NVI idiom)(非虚拟接口惯用法),这是用 public non-virtual member functions(公有非虚拟成员函数)包装可访问权限较小的 virtual functions(虚拟函数)的 Template Method design pattern(模板方法模式)的一种形式。用 function pointer data members(函数指针数据成员)代替 virtual functions(虚拟函数),一种 Strategy design pattern(策略模式)的显而易见的形式。用 tr1::function data members(数据成员)代替 virtual functions(虚拟函数),这样就允许使用兼容于你所需要的东西的 any callable entity(任何可调用实体)。这也是 Strategy design pattern(策略模式)的一种形式。用 virtual functions in another hierarchy(另外一个继承体系中的虚拟函数)代替 virtual functions in one hierarchy(单独一个继承体系中的虚拟函数)。这是 Strategy design pattern(策略模式)的习以为常的实现。这不是一个可选的 virtual functions(虚拟函数)的替代设计的详尽无遗的列表,但是它足以使你确信这些是可选的方法。此外,它们之间互为比较的优劣应该使你考虑它们时更为明确。

为了避免陷入 object-oriented design(面向对象设计)的习惯性道路,时不时地给车轮一些有益的颠簸。有很多其它的道路。值得花一些时间去考虑它们。

Things to Remember

可选的 virtual functions(虚拟函数)的替代方法包括 NVI 惯用法和 Strategy design pattern(策略模式)的各种变化形式。NVI 惯用法本身是 Template Method design pattern(模板方法模式)的一个实例。将一个机能从一个 member function(成员函数)中移到 class(类)之外的某个函数中的一个危害是 non-member function(非成员函数)没有访问类的 non-public members(非公有成员)的途径。tr1::function 对象的行为类似 generalized function pointers(泛型化的函数指针)。这样的对象支持所有兼容于一个给定的目标特征的 callable entities(可调用实体)。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有