举书上的例子,考虑一个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函数以外的其他选择的更多相关文章

  1. 读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承

    private继承的意义在于“be implemented in turns of”,这个与上一条款中说的复合模型的第二层含义是相同的,这也意味着通常我们可以在这两种设计方法之间转换,但书上还是更提倡 ...

  2. 读书笔记_Effective_C++_条款三十四:区分接口继承和实现继承

    这个条款书上内容说的篇幅比较多,但其实思想并不复杂.只要能理解三句话即可,第一句话是:纯虚函数只继承接口:第二句话是:虚函数既继承接口,也提供了一份默认实现:第三句话是:普通函数既继承接口,也强制继承 ...

  3. 读书笔记_Effective_C++_条款三十:了解inline的里里外外

    学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换.这是一种以空间换时间的做法.把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的 ...

  4. 读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系

    这一条款是说的是公有继承的逻辑,如果使用继承,而且继承是公有继承的话,一定要确保子类是一种父类(is-a关系).这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们 ...

  5. 读书笔记_Effective_C++_条款四十五:运用成员函数模板接受所有兼容类型

    比如有一个Base类和一个Derived类,像下面这样: class BaseClass {…}; class DerivedClass : public BaseClass {…}; 因为是父类与子 ...

  6. 读书笔记_Effective_C++_条款三十六:绝不重新定义继承而来的non-virtual函数

    这个条款的内容很简单,见下面的示例: class BaseClass { public: void NonVirtualFunction() { cout << "BaseCla ...

  7. 读书笔记_Effective_C++_条款二十五: 考虑写出一个不抛出异常的swap函数

    在之前的理论上调用对象的operator=是这样做的 void swap(A& x) { std::swap(a, x.a); } A& operator=(const A& ...

  8. 读书笔记_Effective_C++_条款三十八:通过复合塑模出has-a或者is-implemented-in-terms-of

    如果说public是一种is-a的关系的话,那么复合就是has-a的关系.直观来说,复合就是在一个类中采用其他类的对象作为自身的成员变量,可以举个例子,像下面这样: class Person { pr ...

  9. 读书笔记_Effective_C++_条款三十七:绝不重新定义继承而来的缺省参数值

    先看下面的例子: enum MyColor { RED, GREEN, BLUE, }; class Shape { public: ; }; class Rectangle: public Shap ...

随机推荐

  1. 我所知道的MVVM框架(转 司徒大大 )

    RubyLouvre commented on 6 Sep 2014   avalon http://avalonjs.github.io/ (使用Object.defineProperties. V ...

  2. HTML5学习--SVG全攻略(基础篇)

    明天高级篇 一.什么是SVG? SVG 指的是可伸缩矢量图形 (Scalable Vector Graphics),它用来定义用于网络的基于矢量的图形,使用 XML 格式定义图形.SVG 图像在放大或 ...

  3. Linux sudo 配置

    1.配置sudo权限 sudo配置信息保存在 /etc/sudoers 文件中,可以使用vi修改.但使用vi修改需要给root用户设置权限,可以使用 visudo 直接修改. visudo 复制这行将 ...

  4. python对象学习

    python对象的介绍 python使用对象模型来存储数据,构造任何类型的值都是一个对象,尽管python被当成面向对象的编程语言,但是完全编写不使用任何类和实例的脚本.所有的python对象都拥有三 ...

  5. 20165333 学习基础和C语言学习基础

    说实话,我并没有什么技能比90%以上的人更好,非要拿一个出来的话,篮球勉强好一点吧.最初接触篮球是小学的时候跟着哥哥看他打球,哥哥的球技在同龄人中算是好的,每次看他各种突破过人,我都觉得特别潇洒帅气, ...

  6. dubbo的详细介绍

    1.背景 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 2.架构 单一应用架构 当网站流量很 ...

  7. Bootstrap进阶七:LESS语法详解

    1.变量 2.混合 3.带参数混合 4.模式匹配和导引表达式 5.嵌套规则 6.运算 7.Color函数 8.Math函数 9.命名空间 10.作用域 11.注释 12.Importing 13.字符 ...

  8. 关于页面Meta属性

    meta标签的组成 meta标签共有两个属性,它们分别是http-equiv属性和name属性,不同的属性又有不同的参数值,这些不同的参数值就实现了不同的网页功能. 1.name属性 name属性主要 ...

  9. C++实现平衡二叉树

    1.概念 平衡二叉树(AVL Tree)首先要满足二叉树的定义,如下 二叉排序树或者是一棵空树,或者是具有下列性质的二叉树: 若左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若右子树不空, ...

  10. Bzoj2164 采矿(线段树+树链剖分)

    题面 Bzoj 题解 对于每个节点,我们可以用树链剖分和线段树维护以下信息: 单独在某个点分配\(i\)个人的最大收益(可以\(O(m)\)计算) 分配\(i\)的最大收益(可以\(O(m^2)\)计 ...