六、继承与面向对象设计--条款35-37

条款35:考虑virtual函数以外的其他选择

在这个条款里面讨论virtual函数的替代方案。文中主要探讨了两种方式——NVI手法(Template Method模式)和Strategy模式。

一、NVI手法

NVI即Non-Virtual Interface。是Template Method设计模式中特定的一种,算法骨架来自于基类,具体的实现是在子类中实现。

在某个流派中,他们建议virtual函数几乎总是private的,然后使用一个non-virtual的member函数来调用virtual函数。

class GameCharacter
{
public:
    int healthValue() const
    {
        ... // 事前工作
        int retVal = doHealthValue();
        ... // 事后工作
    }
private:
    virtual int doHealthValue()
    {
        ...
    }
};

作者把上述的那个non-virtual函数——healthValue称为virtual函数的外覆器

NVI手法的一个优点在于在调用外覆器之前设定好适当的场景,调用之后清理场景。 个人认为,在这个过程中要注意异常的抛出,要处理好异常。否则,事前如果锁定互斥锁,接着执行virtual函数的时候抛出了异常,那么这个锁要怎么释放呢?

二、古典的Strategy模式

采用函数指针作为计算健康值的计算方法。不作为member函数。

UML图类似如下:
D4CB9146558D4EFAAA59DB5B8D87952A?method=download&shareKey=9ad7716afb09fe9833d3c62424ec4df9

在这个例子的大致做法就是,给定一个non-member的计算健康值的函数,作为Character的构造函数参数,然后使用这个函数去计算健康值。

因为这边涉及到设计模式的方法,所以没有很细致的记录下来,会在后面学习设计模式的博客中分析设计模式。现在,让我们来总结一下替代方案:

  • NVI手法。 以public non-virtual成员函数包裹较低访问性(private or protected)的virtual函数。
  • 将virtual函数替换为“函数指针成员变量”。这是Strategy模式的一种分解表现形式。
  • 以std::tr1::function替换virtual函数。 这也是Strategy模式的一种分解形式。
  • 将继承体系中的virtual函数替换为另一个继承体系内的virtual函数。 这是Strategy模式的传统实现手法。

作者总结

virtual函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。

将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。

tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物。

条款36:绝不重新定义继承而来的non-virtual函数

这一条款其实很简单地就能陈述其原因。先肯定地明确一点:non-virtual函数的不变性凌驾于特异性。 继承一个non-virtual函数,使用public继承就是想要继承一个接口和强制性实现,所以不变性凌驾于特异性。

然后我们再看一眼适合讲述的例子:

class B
{
public:
    void mf();
};
class D : pubic B
{
public:
    void mf();
};

D以public形式继承了B,且重写了一份non-virtual的mf函数。那么B中的non-virtual就会被覆盖。考虑以下执行:

D x;
B *pB = &x;
D *pD = &x;
pB->mf();   // 调用B::mf
pD->mf();   // 调用D::mf

现在,同一个x,调用的肯定会是不同的mf函数实现。这是因为non-virtual函数都是静态绑定的,不是动态绑定,他们会调用各自指针指向类的成员函数。

所以,如果真要有一份不同的mf函数的实现,那么我们就应该考虑其声明为virtual函数。

作者总结

绝对不要重新定义继承而来的non-virtual函数。

条款37:绝不重新定义继承而来的缺省参数值

一、原因

virtual函数是动态绑定(dynamically bound)的,缺省参数值却是静态绑定的(statically bound)。

二、从代码中看到错误

#include <iostream>

using namespace std;

class Shape
{
public:
    enum ShapeColor
    {
        Red,
        Green,
        Blue
    };
    virtual void draw(ShapeColor color = Red) = 0;  // 默认参数是Red
    inline void ShowColor(ShapeColor color)
    {
        if (color == 0)
        {
            cout << "Red" << endl;
        }
        else if (color == 1)
        {
            cout << "Green" << endl;
        }
        else if (color == 2)
        {
            cout << "Blue" << endl;
        }
        else
        {
            cout << "invalid color" << endl;
        }
    }
};
class Rectangle : public Shape
{
public:
    virtual void draw(ShapeColor color = Green)     // 默认参数是Green
    {
        cout << "default parameter : Green, But the real parameter:";
        ShowColor(color);
    }
};
class Circle : public Shape
{
public:
    virtual void draw(ShapeColor color = Blue)  // 默认参数是Blue
    {
        cout << "default parameter : Blue , But the real parameter:";
        ShowColor(color);
    }
};
class Triangle : public Shape
{
public:
    virtual void draw(ShapeColor color)     // 默认参数是Blue
    {
        cout << "No default parameter" << endl;
        ShowColor(color);
    }
};

int main()
{
    Shape *pR = new Rectangle;
    Shape *pC = new Circle;
    Shape *pT = new Triangle;

    pR->draw();
    pC->draw();
    pT->draw(Shape::Green);
    return 0;
}

七十来行的代码,讲一下重点:

(1) 抽象基类中有一个纯虚函数draw,缺省参数值为Red

(2) Rectangle重写draw,默认参数值为Green。

(3) Circle的重写draw,默认参数值为Blue。

(4) Triangle重写draw,没有默认参数值。

(5) 接下来我们用一个Shape类的指针,分别指向三个继承类。

(6) 然后分别调用他们的draw函数。

讲道理的话,pR调用的应该是自己的draw函数,默认参数为Green,同理,pC的默认参数为Blue。事实上的执行结果:

491FAB06C99544018E1144602E4DF17E?method=download&shareKey=a6a58d0fbeeb36048b0c78dd536444fe

三、缺省参数值是静态绑定的

以上的例子已经完美说明了,virtual是动态绑定的,而缺省参数值却是静态绑定的。

  • 因为virtual是动态绑定的,所以指向derived classes的base指针会寻找到正确的virtual函数去执行。
  • 因为默认参数值是静态绑定的,所以使用的缺省参数值还是base类的缺省参数Red。

四、为何支持这种看似错误的方式?

如果编译器没有给我们这样的默认参数一个错误或者警告提示,为何不像我们想的那样,将缺省参数值也进行动态绑定呢?

为了运行期效率。 如果编译器要执行某种方法将运行期的virtual的函数决定适当的缺省参数值,比现在这种“在编译器决定”的机制更加复杂且更加慢。

作者总结

绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定的。

转载于:https://www.cnblogs.com/love-jelly-pig/p/9699371.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值