读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择
举书上的例子,考虑一个virtual函数的应用实例:
class GameCharacter
{
private:
int BaseHealth;
public:
virtual int GetHealthValue() const // 返回游戏人物的血量
{
return BaseHealth;
} int GetBaseHealth() const
{
return BaseHealth;
}
}; class KnightBoss : public GameCharacter
{
public:
virtual int GetHealthValue() const
{
return GetBaseHealth() * ;
}
}; int main()
{
GameCharacter *CommonCharacter = new GameCharacter();
GameCharacter *Boss = new KnightBoss();
cout << "CommonCharacter heath = " << CommonCharacter->GetHealthValue() << endl; //返回100
cout << "KnightBoss heath = " << Boss->GetHealthValue() << endl; // 返回200
}
GetHealthValue会根据不同类型的游戏角色来获得相应的血量。但这里将虚函数是public的,NVI(Non-Virutal Interface)的一个流派主张所有的虚函数都是private的,将父类与子类都会使用的前置方法与后置方法单独作一个non-virtual的函数,像下面这样:
class GameCharacter
{
private:
int BaseHealth;
public:
GameCharacter(int bh = ) :BaseHealth(bh){} int GetBaseHealth() const
{
return BaseHealth;
} int GetHealthValue()
{
cout << "可以是一些前置检查" << endl;
int Result = DoGetHealthValue();
cout << "可以是一些后置处理" << endl;
return Result;
} private:
virtual int DoGetHealthValue() const // 返回游戏人物的血量
{
return BaseHealth;
}
}; class KnightBoss : public GameCharacter
{
public:
KnightBoss(int bh) :GameCharacter(bh){} private:
virtual int DoGetHealthValue() const
{
return GetBaseHealth() * ;
}
};
main函数的接口不必有任何的变化。这里是把父类与子类有特异性的方法都写在了各自private范围内。这样的好处,是可以做一些善前善后的事情(如程序中的cout方法所示),善前方法可以是锁定互斥器,打印日志,验证输入数据合法性等,善后的方法可以是解除锁定,验证事后数据合法性等。
注意这里是将虚函数都置成了private的,但编译器生成的虚表指针则不是private的,否则会因private成员变量根本不被继承而无法实现多态。NVI的方式也不是绝对的,比如虚析构函数,它必须是public的,才能确保它的子类,以及子类的子类们能够顺利释放资源。
那么还有其他方法来替代virtual函数吗?virtual函数的本质是由虚指针和虚表来控制,虚指针指向虚表中的某个函数入口地址,就实现了多态。因此,我们也可以仿照一个虚指针指向函数的手法,来做一个函数指针。像下面这样:
class GameCharacter
{
private:
int BaseHealth;
public:
typedef int(*HealthCalcFunc)(const GameCharacter&);
GameCharacter(int bh = , HealthCalcFunc Func= NULL)
:BaseHealth(bh), HealthFuncPoniter(Func){} int GetHealthValue()
{
if (HealthFuncPoniter)
{
return HealthFuncPoniter(*this);
}
else
{
return ;
}
} int GetBaseHealth() const
{
return BaseHealth;
}
private:
HealthCalcFunc HealthFuncPoniter;
}; class KnightBoss : public GameCharacter
{
public:
KnightBoss(int bh, HealthCalcFunc Func) :GameCharacter(bh, Func){} private: }; int HealthCalcForCommonMonster(const GameCharacter& gc)
{
return gc.GetBaseHealth();
} int HealthCalcForKnightBoss(const GameCharacter& gc)
{
return * gc.GetBaseHealth();
}
main函数的内容仍然不需要改变,得到的结果是相同的,其实这里只是用函数指针模拟了虚表指针而已。在父类中声明了这个函数指针的形式,它返回一个int值,但因为需要用到GameCharacter里面的方法,所以形参是GameCharater的引用。父类的有一个私有的成员变量,它就是可以指向具体函数的函数指针。在父类与子类的构造函数里带入不同的函数,就可以实现调用父类GetHealthValue时产生的不同计算方法。
上面是用了typedef进行了函数指针类型声明,然后定义了指定形参与返回值的函数,在构造时将类中的函数指针指向特定的函数,那么我们就会想,能不能将用类(而不是typedef)来做呢?请看下面的示例:
class HealthCalcFunctionBaseClass
{
public:
virtual int CalcHealth(int BaseValue) // 书上这里用的是const GameCharacter&,然后前置声明了class GameCharacter,但因为要用到GameCharacter的具体方法,所以编译器会报错,不知道你们是如何解决这个问题的
{
return BaseValue;
}
}; class HealthCalcFunctionDerivedClass : public HealthCalcFunctionBaseClass
{
public:
virtual int CalcHealth(int BaseValue)
{
return * BaseValue;
}
}; class GameCharacter
{
private:
int BaseHealth;
public:
GameCharacter(int bh = , HealthCalcFunctionBaseClass *Func = NULL)
:BaseHealth(bh), HealthFuncPoniter(Func){} int GetHealthValue()
{
if (HealthFuncPoniter)
{
return HealthFuncPoniter->CalcHealth(GetBaseHealth());
}
return ;
} int GetBaseHealth() const
{
return BaseHealth;
}
private:
HealthCalcFunctionBaseClass *HealthFuncPoniter;
}; class KnightBoss : public GameCharacter
{
public:
KnightBoss(int bh, HealthCalcFunctionBaseClass *Func) :GameCharacter(bh, Func){}
}; // main函数的接口需要改一下
int main()
{
HealthCalcFunctionBaseClass *CommonMonsterCalc = new HealthCalcFunctionBaseClass();
HealthCalcFunctionBaseClass *BossCalc = new HealthCalcFunctionDerivedClass();
GameCharacter *CommonCharacter = new GameCharacter(, CommonMonsterCalc);
GameCharacter *Boss = new KnightBoss(, BossCalc); cout << "CommonCharacter heath = " << CommonCharacter->GetHealthValue() << endl; //返回100
cout << "KnightBoss heath = " << Boss->GetHealthValue() << endl; // 返回200
}
有的读者会说,既然是在讨论如果用其他方法来替换virtual函数,但这里用来替换virtual函数的还是一个带virtual的类啊,的确,这只是作者的应用strategy模式的一种实现方式而已,第一种NVI方法本身也是离不开private的virutal函数,如果你觉得矛盾,那就还是用纯函数指针的方法吧。(作者也许表达的是想用一种特殊的virtual形式,去代替我们通常意义上见到的virtual函数吧)
书上还说到仿函数的替换方法,有兴趣可以看看。下面总结一下:
1. virtual函数的替代方案NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式;
2. 将机能从成员函数转移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择的更多相关文章
- 读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承
		
private继承的意义在于“be implemented in turns of”,这个与上一条款中说的复合模型的第二层含义是相同的,这也意味着通常我们可以在这两种设计方法之间转换,但书上还是更提倡 ...
 - 读书笔记_Effective_C++_条款三十四:区分接口继承和实现继承
		
这个条款书上内容说的篇幅比较多,但其实思想并不复杂.只要能理解三句话即可,第一句话是:纯虚函数只继承接口:第二句话是:虚函数既继承接口,也提供了一份默认实现:第三句话是:普通函数既继承接口,也强制继承 ...
 - 读书笔记_Effective_C++_条款三十:了解inline的里里外外
		
学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换.这是一种以空间换时间的做法.把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的 ...
 - 读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系
		
这一条款是说的是公有继承的逻辑,如果使用继承,而且继承是公有继承的话,一定要确保子类是一种父类(is-a关系).这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们 ...
 - 读书笔记_Effective_C++_条款四十五:运用成员函数模板接受所有兼容类型
		
比如有一个Base类和一个Derived类,像下面这样: class BaseClass {…}; class DerivedClass : public BaseClass {…}; 因为是父类与子 ...
 - 读书笔记_Effective_C++_条款三十六:绝不重新定义继承而来的non-virtual函数
		
这个条款的内容很简单,见下面的示例: class BaseClass { public: void NonVirtualFunction() { cout << "BaseCla ...
 - 读书笔记_Effective_C++_条款二十五: 考虑写出一个不抛出异常的swap函数
		
在之前的理论上调用对象的operator=是这样做的 void swap(A& x) { std::swap(a, x.a); } A& operator=(const A& ...
 - 读书笔记_Effective_C++_条款三十八:通过复合塑模出has-a或者is-implemented-in-terms-of
		
如果说public是一种is-a的关系的话,那么复合就是has-a的关系.直观来说,复合就是在一个类中采用其他类的对象作为自身的成员变量,可以举个例子,像下面这样: class Person { pr ...
 - 读书笔记_Effective_C++_条款三十七:绝不重新定义继承而来的缺省参数值
		
先看下面的例子: enum MyColor { RED, GREEN, BLUE, }; class Shape { public: ; }; class Rectangle: public Shap ...
 
随机推荐
- Fiddler是最强大最好用的Web调试工具
			
Fiddler是最强大最好用的Web调试工具之一,它能记录所有客户端和服务器的http和https请求,允许你监视,设置断点,甚至修改输入输出数据. 使用Fiddler无论对开发还是测试来说,都有很大 ...
 - servlet Filter过滤javascript
			
新建HttpServletRequestWrapper子类XssHttpServletRequestWrapper import javax.servlet.http.HttpServletReque ...
 - NLP里面好的学习资料
			
别人推荐的网址: http://ruder.io/deep-learning-nlp-best-practices/index.html#wordembeddings
 - Fiddler 4 抓包(APP HTTPS )
			
一.手机连接Fiddler 1.配置fiddler 1.安装fiddler,基本下一步下一步即可: 2.打开fiddler,点击顶部栏Tools——>Options 3.在HTTPS页签勾选“D ...
 - makefile特殊符号介绍
			
http://blog.chinaunix.net/uid-20564848-id-217918.html makefile下$(wildcard $^),$^,$@,$?,$<,$(@D),$ ...
 - 如何使用 JMeter 调用你的 Restful Web Service?进行简单的压力测试和自动化测试
			
表述性状态传输(REST)作为对基于 SOAP 和 Web 服务描述语言(WSDL)的 Web 服务的简单替代,在 Web 开发上得到了广泛的接受.能够充分证明这点的是主流 Web 2.0 服务提供商 ...
 - MS-SQL2005服务器登录名、角色、数据库用户、角色、架构的关系
			
MS SQL2005对2000进行了很大的改进,而用户关系这部分也变得相当复杂了,很多朋友都对此一知半解!下面,我将把我应用中总结的和大家分享下,先从概念入手,希望对不理解的朋友有点提示. 今天我们要 ...
 - CVE-2012-4969
			
Microsoft Internet Explorer ‘CMshtmlEd::Exec’函数释放后使用漏洞(CNNVD-201209-394) Microsoft Internet Explorer ...
 - java解析Xml格式的字符串
			
最近在工作中,需要调别的接口,接口返回的是一个字符串,而且内容是xml格式的,结果在解析json的时候报错,最终修改了接口的返回方式,以Map返回, 才得以接收到这个xml的字符串,然后通过dom4j ...
 - switch case语句
			
五.switch case语句 1.格式 Switch(表达式) { case 表达式:语句块 break: … default break: } 2.例题 输入年份.月份.日期,判断是否是闰年,并且 ...