zoukankan      html  css  js  c++  java
  • effective c++:virtual函数的替代方案

    绝不重新定义继承来的缺省值

    首先明确下,虚函数是动态绑定,缺省参数值是静态绑定

    // a class for geometric shapes
    class Shape {
    public:
      enum ShapeColor { Red, Green, Blue };
      // all shapes must offer a function to draw themselves
      virtual void draw(ShapeColor color = Red) const = 0;
      ...
    };
    class Rectangle: public Shape {
    public:
      // notice the different default parameter value — bad!
      virtual void draw(ShapeColor color = Green) const;
      ...
    };
    class Circle: public Shape {
    public:
      virtual void draw(ShapeColor color) const;
      ...
    };

    当我们这样指定这些指针时。

    Shape *ps; // static type = Shape*
    Shape *pc = new Circle; // static type = Shape*
    Shape *pr = new Rectangle; // static type = Shape*

    三个对象的静态类型均为Shape*,这时调用它们的虚函数:

    pc->draw(Shape::Red); // calls Circle::draw(Shape::Red)
    pr->draw(Shape::Red); // calls Rectangle::draw(Shape::Red)

    由于虚函数是动态绑定的,所以pc,pr调用各自的draw没错,但是却是用的时shape中draw指定的Red参数值,当运行这段代码时,画出来的图像可能就是个四不像。

    所以如果基类成员虚函数中定义了缺省值,就不能用常规的方法来使用它,我们需要考虑它的替代设计:

    class Shape {
    public:
    enum ShapeColor { Red, Green, Blue };
    void draw(ShapeColor color = Red) const // now non-virtual
    {
      doDraw(color); // calls a virtual
    }
    ...
    private:
    virtual void doDraw(ShapeColor color) const = 0; // the actual work is
    }; // done in this func

    class Rectangle: public Shape { public:   ... private:   virtual void doDraw(ShapeColor color) const; // note lack of a   ... // default param val. };

    派生类可以重新定义虚函数,而且可以指定自己独有的color,而non-virtual函数draw按照c++设计原则是不应该被重写的。这种方法称为non-virtual interface,但是有一些函数根据实际需求必须是public,比方多态性质的基类析构函数,这样一来就不能使用NVI了。

    tr1::function实现Strategy设计模式

    class GameCharacter; // as before
    int defaultHealthCalc(const GameCharacter& gc); // as before
    class GameCharacter {
    public:
      // HealthCalcFunc is any callable entity that can be called with
      // anything compatible with a GameCharacter and that returns anything
      // compatible with an int; see below for details
      typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
      : healthFunc(hcf )
      {}
      int healthValue() const
      { return healthFunc(*this); }
        ...
    private:
      HealthCalcFunc healthFunc;
    };

    上例中我们把HealthCalcFunc声明为一个typedef,他接受一个GameCharacter引用类型的参数,返回int.tr1::function的功能是使可调物的参数隐式的转换为const GameCharacter&,同理返回类型隐式的转换为int。

    我们可以调用改函数进行计算,使用这种方式来替代虚函数的设计避免了NVI的问题,又展现了出色的兼容性。

    short calcHealth(const GameCharacter&); // health calculation
    // function; note
    // non-int return type
    struct HealthCalculator { // class for health
    int operator()(const GameCharacter&) const // calculation function
    { ... } // objects
    };
    class GameLevel {
    public:
    float health(const GameCharacter&) const; // health calculation
    ... // mem function; note
    }; // non-int return type
    class EvilBadGuy: public GameCharacter { // as before
    ...
    };
    class EyeCandyCharacter: public GameCharacter { // another character
    ... // type; assume same
    }; // constructor as
    // EvilBadGuy
    EvilBadGuy ebg1(calcHealth); // character using a
    // health calculation
    // function
    EyeCandyCharacter ecc1(HealthCalculator()); // character using a
    // health calculation
    // function object
    GameLevel currentLevel;
    ...
    EvilBadGuy ebg2( // character using a
    std::tr1::bind(&GameLevel::health, // health calculation
    currentLevel, // member function;
    _1) // see below for details
    );

    为了计算ebg2的健康指数,应该使用GameLevel class的成员函数health。GameLevel::haelth宣称它自己接受一个参数(那是个引用指向GameCharacter),但它实际上接受两个参数,因为它也获得了一个隐式参数gameLevel,也就是this所指的那个。然而GameCharacter的健康计算函数只接受单一参数:GameCharacter(这个对象将被计算出健康指数)。如果我们使用GameLevel::health作为ebg2的健康计算函数,我们必须以某种方式转换它,使它不再接受两个参数(一个GameCharacter和一个GameLevel),转而接受单一参数(一个GameCharacter)。这个例子中我们必然会想要使用currentLevel作为“ebg2的健康计算函数所需的那个GameLevel对象”,于是我们将currentLevel绑定为GameLevel对象,让它在“每次GameLevel::health被调用以计算ebg2的健康”时被使用。那正是tr1::bind的作为:它指出ebg2的健康计算函数总是以currentLevel作为GameLevel对象。

    传统Strategy模式

    class GameCharacter; // forward declaration
    class HealthCalcFunc {
    public:
    ...
    virtual int calc(const GameCharacter& gc) const
    { ... }
    ...
    };
    HealthCalcFunc defaultHealthCalc;
    class GameCharacter {
    public:
    explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
    : pHealthCalc(phcf )
    {}
    int healthValue() const
    { return pHealthCalc->calc(*this); }
    ...
    private:
    HealthCalcFunc *pHealthCalc;
    };

    这个设计方案是将virtual函数替换为另一个继承体系中的virtual函数。

  • 相关阅读:
    云存储研发工程师(40-50万)
    数据分析师(50-70万)
    云计算-资深java研发
    云计算 -- 资深python开发
    公众号”IT高薪猎头“
    51内核mcu实现printf的一种方法
    一种基于蓝牙BLE无线控制的灯光系统的解决方案
    Ecx后台增加新菜单+新数据表+新bundle完整过程
    Ecx 生成swagger文档
    ecshopx-manage管理后台本地编译设置本地API
  • 原文地址:https://www.cnblogs.com/loujiayu/p/3636962.html
Copyright © 2011-2022 走看看