• EC读书笔记系列之16:条款35、36、37、38、39、40


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

    记住:

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

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

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

    ------------------------------------------------------------------------------------------

    背景:

    class GameCharacter {

           public:

                  virtual int healthValue() const; //返回角色的健康指数

                  ...

    };

    这是常规设计,以下探讨其替代解决方案。

    借由non-virtual interface手法实现template method模式:

      NVI手法对public virtual函数而言是一个有趣的替代方案。令客户通过public non-virtual成员函数间接调用private virtual函数,称为NVI手法。它是所谓的template method模式的一个独特表现形式(注意此处应将其与看过的设计模式知识联系起来!!!)。此public non-virtual函数称为virtual函数的外覆器(wrapper

    class GameCharacter {
        public:
            int healthValue() const {       //此即public non-virtual member 函数
                ...                              //做一些事前工作
                int retVal = doHealthValue();       //做真正的工作
                ...                            //做一些事后的工作
                return retVal;
            }
            ...
        private:
            virtual int doHealthValue() const { //派生类可以重新定义它!!!
                ...
            }
    };

    注意点:

    a NVI手法一个优点隐身在上述代码注释“做一些事前、事后工作”之中。事前工作可包括锁定互斥器、制造运转日志记录项、验证class约束条件、验证函数先决条件等;事后工作包括互斥器解锁、验证函数的事后条件、再次验证class约束条件等等。

    b 注意一个C++规则:derived classes可重写继承而来的private virtual函数!!!!!!!

    ----------------

    借由函数指针实现策略模式:(注意联系到essential C++笔记中的通过面向过程的方法来实现多态性的数列类例子!!!)

    class GameCharacter;
    
    //以下函数是计算健康指数的缺省算法
    int defaultHealthCalc( const GameCharacter &gc );
    
    class GameCharacter { //类比于strategy模式的context类
        public:
            typedef int (*HealthCalcFunc)( const GameCharacter & );
    
            explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc )
            : healthFunc( hcf ) { }
            
            int healthValue() const {
                return healthFunc( *this );
            }
            
        private:
            HealthCalcFunc healthFunc;    //类比于context中的策略基类!!!
    };

    此做法是常见的策略模式的简单应用。拿它和“基于GameCharacter继承体系内之virtual函数”的做法比较,提供了以下弹性:

    ♢ 同一人物类型之不同实体可以有不同的健康计算函数,如:

    class EvilBadGuy : public GameCharacter {
        public:
            explicit EvilBadGuy( HealthCalcFunc hcf = defaultHealthCalc )
            : GameCharacter( hcf ) { }
            ...
    };
    
    int loseHealthQuickly( const GameCharacter & );  //健康指数计算函数1
    int loseHealthSlowly( const GameCharacter & );   //健康指数计算函数2
    
    EvilBadGuy ebg1( loseHealthQuickly ); //相同类型的人物搭配不同的健康计算方式
    EvilBadGuy ebg2( loseHealthSlowly );

    ♢ 某已知人物之健康指数计算函数可在运行期变更。如GameCharacter可提供一个成员函数setHealthCalculator用来替换当前的健康指数计算函数。

    -------------------------------

    借由tr1::function实现策略模式:

        即对上面方法的改进:此处不使用function pointer,而是改用一个类型为tr1::function的对象,这样的对象可持有任何可调用物(包括函数指针、仿函数、成员函数指针),只要其签名式兼容于需求端:

    class GameCharacter;   //如前
    
    //以下函数是计算健康指数的缺省算法
    int defaultHealthCalc( const GameCharacter &gc ); //如前
    
    class GameCharacter {
        public:
            //HealthCalcFunc可以是任何可调用物,可被调用并接收任何兼容于GameCharacter
            //之物,返回任何兼容于int的东西
            typedef std::tr1::function<int (const GameCharacter &)> HealthCalcFunc; //注意语法形式!!!
            explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc )
            : healthFunc( hcf ) { }  
            
            int healthValue() const {
                return healthFunc( *this );
            }
            
        private:
            HealthCalcFunc healthFunc;
    };

    如今GameCharacter持有一个tr1::function对象,相当于一个指向函数的泛化指针!!!此时使用起来弹性更大:

    short calcHealth( const GameCharacter & ); //健康指数计算函数,注意其返回类型为non-int
    
    struct HealthCalculator {
        int operator()( const GameCharacter & ) const //为健康计算而设计的仿函数
        {...}
    };
    
    class GameLevel {
        public:
            float health( const GameCharacter & ) const; //成员函数,也可用来计算健康指数
            ...                                             //注意其non-int返回类型
    };
    
    class EvilBadGuy : public GameCharacter {  //同前
        ...
    };
    
    class EyeCandyCharacter : public GameCharacter {  //另一人物类型,假设其构造函数与
                                                      //EvilBadGuy相同
        ...
    };
    
    EvilBadGuy ebg1( calcHealth );                 //人物1,使用其他函数计算健康指数
    EyeCandyCharacter ecc1( HealthCalculator() ); //人物2,使用functer计算健康指数
    
    GameLevel currentLevel;
    ...
    EvilBadGuy ebg2( std::tr1::bind( &GameLevel::health, currentLevel, _1 ) );
    //人物3,使用某个成员函数计算健康指数,注意此语法形式!!!

    ------------

    总结本条款提出的几个virtual函数的替代方案:

    ♢ 使用NVI手法,其以public non-virtual成员函数包裹较低访问性(private或protected)的virtual函数(这是模板方法模式的一种特殊形式)

    ♢ 将virtual函数替换为“函数指针成员变量”(这是策略模式的一种分解表现形式)

    ♢ 以tr1::function成员变量替换virtual函数 (这也是策略模式的某种形式)

    条款36 绝不重写继承而来的non-virtual函数

    记住:

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

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

    记住:

    ★绝不重新定义继承而来的缺省参数值,∵缺省参数值都是静态绑定,而virtual函数---你唯一应该覆写的东西---却是动态绑定

    ----------------------------------------------------

    本条款的讨论主题是“继承一个带有缺省参数值的virtual函数”。

    class Shape {
        public:
            enum ShapeColor { Red, Green, Blue }; //Red是0
            virtual void Draw( ShapeColor color = Red ) const = 0;
            ...
    };
    
    class Rectangle : public Shape {
        public:
            //注意,赋予了不同的缺省参数值,糟糕!!!
            virtual void Draw( ShapeColor color = Green ) const;
            ...
    };
    
    class Circle : public Shape {
        public:
            virtual void Draw( ShapeColor color ) const;
            //以上这么写则当客户以对象调用此函数,一定要指定参数值,∵静态绑定
            //下这个函数并不从其base继承缺省参数值。但若以指针或引用调用此函数
            //可不指定参数值,∵动态绑定下这个函数会从其base继承缺省参数值
            ...
    };
    
    Shape *pc = new Circle;
    Shape *pr = new Rectangle; //pr的静态类型是Shape*,动态类型是Rectangle*!!!
    pr->Draw(); //此处可不指定参数值,调用Rectangle::Draw( Shape::Red )

    此处pr的动态类型是Rectangle*,∴调用的是Rectangle的virtual函数。Rectangle::Draw函数的缺省参数值应该是Green,但由于pr的静态类型是Shape*∴此一调用的缺省参数值来自Shape class而非Rectangle class!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    --------------

    若你试着遵循此条款,且同时提供缺省参数给base和derived class又会怎么样?

    class Shape {
        public:
            enum ShapeColor { Red, Green, Blue }; //Red是0
            virtual void Draw( ShapeColor color = Red ) const = 0;
            ...
    };
    
    class Rectangle : public Shape {
        public:
            virtual void Draw( ShapeColor color = Red ) const;
            ...
    };

    这就代码重复了!!!更糟糕的是代码重复又带来了相依性:若Shape内的缺省参数值改变了,所有“重复给定缺省参数值”的那些derived classes也必须改变!!!

    --------------------------

    当你想令virtual函数表现出你所想要的行为但却遭遇麻烦,聪明的做法是考虑替代设计:

    NVI手法

    class Shape {
        public:
            enum ShapeColor { Red, Green, Blue }; //Red是0
            void Draw( ShapeColor color = Red ) const {  //wrapper!!!
                doDraw( color );
            }
        ...
        
        private:
            virtual void doDraw( ShapeColor color ) const = 0;
    };
    
    class Rectangle : public Shape {
        public:
            ...
        private:
            virtual void doDraw( ShapeColor color ) const;
            ...
    };

    由于non-virtual函数绝不会被derived classes覆写,这个设计很清楚地使得draw函数的color缺省参数值总是为Red

    条款38 通过复合塑模出has-a或“根据某物实现出”

    记住:

    ★复合的意义与public继承完全不同

    ★在应用域复合意味has-a;在实现域符合意味is-implemented-in-terms-of(根据某物实现出)

    条款39 明智而审慎地使用private继承

    记住:

    private继承意味is-implemented-in-terms-of。它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这个设计是合理的

    ★和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要

    -----------------------------------------------------------------------------------------------------

     private继承的两个典型特点

    1 若class之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象:

    class Person {...};
    class Student : private Person {...};
    void eat( const Person &p );
    Person P;
    Student s;
    eat( p ); //
    eat( s ); //错误!!!

    2 基类中的public和protected成员private继承来之后全变private

    -------------------------------------

    private继承复合一样意味is-implemented-in-terms-of。但它通常比复合的级别低,即原则上尽量使用复合,必要时才使用private继承,这个必要体现在:空间方面的厉害关系

    class Timer {
        public:
            explicit Timer( int tickFrequency );
            virtual void onTick() const;
            ...
    };
    
    class Widget : private Timer {
        private:
            virtual void onTick() const;
            ...
    };

    设计时Timer和Widget并不是将其设计成一个继承体系,即不是is-a关系,我们只想在Widget中用到定时器的onTick而已,所以此处用public继承并不合适,∴选择了private继承。

    此设计的一个替代方案如下(使用复合):

    class Widget {

           private:

                  class WidgetTimer : public Timer { //类中定义类,可以!!!

                         public:

                                virtual void onTick() const;

                                ...

                  };

                 

                  WidgetTimer timer; //复合!!!

                  ...

    };

    下面讲一种必要情况下(即涉及空间利害关系)使用private继承比复合要好:

      这个情况非常激进只适用于所处理的class不带任何数据(这样的class无non-static成员变量;无virtual函数,因为virtual函数会给对象带来vptr;也无virtual base classes):

      class Empty { };

      //此为复合方式

      class HoldsAnInt {

             private:

                    int x;

                    Empty e; //C++规定凡是独立(非附属)对象必须有非0大小

      };

      这种适用复合设计会有sizeof( HoldsAnInt ) > sizeof( int );

    若采用private继承:

      class HoldsAnInt : private Empty {

             private:

                    int x;

      };

    则有sizeof( HoldsAnInt ) == sizeof( int )。这是所谓的EBO(empty base optimization;空白基类最优化)。这样的实践很少增加derived classes的大小,挺好的!!!

    注:现实生活中所指的empty class并不真的是空,其往往含有typedefs、enums、static成员变量或non-virtual函数。

    条款40 明智而审慎地使用多重继承(未看)

    记住:

    ★多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要

    ★virtual继承会增加大小、速度、初始化(及赋值)复杂度等成本。若virtual base classes不带任何数据,将是最具实用价值的情况

    ★多重继承的确有正当用途。其中一个情节涉及“public继承某个interface class”和“private继承某个协助实现的class”的两相组合

  • 相关阅读:
    mysql优化思路
    mysql列类型选择
    mysql 多列索引的生效规则
    Myisam索引和Innodb索引的区别
    mysql创建远程用户并授权
    mysql 索引长度和区分度
    php 内存共享shmop源码阅读
    短链接系统的算法原理
    PHP die与exit的区别
    MySQL建立外键(Foreign Key)
  • 原文地址:https://www.cnblogs.com/hansonwang99/p/4961313.html
Copyright © 2020-2023  润新知