前言
virtual基本是面试必面的知识点了。我们对virtual的认知就只是「想要实现多态,就得用它」。当然这没错,但是virtual的使用还有许许多多的事项和坑点需要注意的。
代替方案
首先考虑,我们非得用多态吗?
书中给了另外一种方案:策略模式。
所谓策略模式,简而言之,不同实例可以有不同的策略。比如,对于武器伤害,有一种伤害方式为直接掉血,另一种伤害方式则是给被伤害者附加一个debuff,那么我们可以传入一个伤害策略给武器,从而实现不同的武器伤害效果。
对于C++,我们可以通过传入函数指针来实现。
1 |
|
当然,通过使用std::bind
+ std::function
,我们也可以让成员函数成为策略。
坑点:默认参数的静态绑定
结论先行:
如果派生类重新定义了虚函数,并且改变了参数的默认值,使用的时候可能会有意料不到的结果。简单来说,当静态类型(持有类型)是基类的时候,会使用基类的默认参数,当静态类型是派生类的时候,会使用派生类的默认参数。
这是由于:virtual的函数是动态绑定的,但是其默认参数却是静态绑定的。
我们看看下面的代码:
1 | using namespace std; |
输出:
1 | Color: 2 |
解决方案
很简单,virtual函数不要带参数默认值。一旦这么做了,继承者也就没有办法更改参数默认值了,毕竟一开始基类虚函数就不存在参数默认值(笑)。
如果我们需求上还是需要参数的默认值,解决方法是新建一个non-virtual函数,让它来做参数默认值,然后再把这个值传给virtual方法,比如我们可以把上面的例子改成这样:
1 | class Painter |
坑点:构造函数调用虚函数
结论先行:
在基类的构造和析构函数中调用虚函数,即使虚函数被派生类重写了,调用的也永远会是基类版本。
一句话解释原理:构造和析构函数的时候,没有多态。
同样举上面Painter的例子:
1 | class Painter |
输出:
1 | Base Color: 1 |
除此之外,还要注意另一种比较隐蔽的坑,那就是构造函数或析构函数中间接调用虚函数。同样的,没有多态,不讲道理。
比如:
1 | class Painter |
解决方案
不要使用virtual。
用传入参数解决
我们使用virtual不就是想要不同的逻辑?如果不同的逻辑是由某个变量决定的,那么就把这个变量变成函数的参数来传入,把函数变回non-virtual。
1 | void Painter::Foo(string Message); |
用策略模式
跟前文提到的一样,想要实现不同的功能,可以由外界传入一个策略来解决。
1 | class Painter |