举书上的例子,考虑一个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. python configparser配置文件解析器

    一.Configparser 此模块提供实现基本配置语言的ConfigParser类,该语言提供类似于Microsoft Windows INI文件中的结构.我们经常会在一些软件安装目录下看到.ini ...

  2. Animate.css 前端动画开发教程

    1.首先下载animate.css文件: 2.打开动画预览地址选择想要的动画,地址:https://daneden.github.io/animate.css/  ,选择好后记住动画的名字在你下载的a ...

  3. python网络编程-线程队列queue

    一:线程queu作用 Python中,queue是线程间最常用的交换数据的形式. 队列两个作用:一个是解耦,一个是提高效率 二:语法 1)队列的类 class queue.Queue(maxsize= ...

  4. 虚拟机 SUSE Linux Enterprise Server 12 SP2 64

    下载地址:https://www.suse.com/zh-cn/products/server/download/ 下载以后使用虚拟机安装即可

  5. Luogu P1566 【加等式】

    看到这道题,我们首先注意到“找出其所有的加等式的个数”,自然地考虑运用计数DP求出若干数相加的和的个数 考虑将每个元素排序后DP处理若干数相加的和的个数 用f[i]表示 对于一个数a[i],对于前i- ...

  6. 20165203《Java程序设计》第八周学习总结

    20165203<Java程序设计>第八周学习总结 教材学习内容总结 第12章 进程与线程 进程的完成过程:代码加载.执行至执行完毕 线程:一个进程由多个线程组成. 线程的完成过程:自身的 ...

  7. 20155225 实验四《Android程序设计》实验报告

    20155225 实验四<Android程序设计>实验报告 一.安装Android Stuidio 问题一:安装完成后,打开提示我找不到SDK,我已经设置了环境变量,关掉提示,没有影响. ...

  8. C#基础系列 - 抽象类及其方法的学习

    在C#中使用关键字 abstract 来定义抽象类和抽象方法. 不能初始化的类被叫做抽象类,它们只提供部分实现,但是另一个类可以继承它并且能创建它们的实例. "一个包含一个或多个纯虚函数的类 ...

  9. PHP array_key_exists() 函数(判断某个数组中是否存在指定的 key)

    定义和用法 array_key_exists() 函数判断某个数组中是否存在指定的 key,如果该 key 存在,则返回 true,否则返回 false. 语法 array_key_exists(ke ...

  10. 13 在 O(1) 时间内删除链表节点

    删除链表的一个结点,用下一个结点覆盖掉要删除的结点,再释放掉要删结点的下一个结点的内存 Java: public ListNode deleteNode(ListNode head, ListNode ...