【读书笔记】Effective C++(06)继承与面向对象

栏目: C++ · 发布时间: 5年前

6. 继承与面向对象

  • 6.1 条款32:确定你的public继承是is-a关系

    • public继承的子类对象需要保证可以被视作父类对象来调用函数。
    • class Person {...};
      class Student : public Person {...};
      
      void eat(const Person& p);
      void study(const Student& s);
      eat(p);   // ok
      eat(s);   // ok,Student可以视作Person调用函数
      study(s);   //ok
      study(p);   //error,Person不能视作Student
  • 6.2 条款33:避免遮掩继承而来的名称

    • int x;  //global变量
      void someFunc() {
          double x;  //local变量
          std::cin >> x;  //local变量的x遮掩了global变量的x,实际起作用的是local变量的x
      }
    • //定义基类
      class Base {
      private:
          int x;
      public:
          virtual void mf1() = 0;
          virtual void mf1(int);
          virtual void mf2();
          void mf3();
          void mf3(double);
          ...
      };
      //定义派生类
      class Derived : public Base {
      public:
          virtual void mf1();
          void mf3();
          void mf4();
          ...
      };
      
      //使用
      Derived d;
      int x;
      ...
      d.mf1();    //ok,调用Derived::mf1
      d.mf1(x);   //bad,因为Derived::mf1遮掩了Base::mf1
      d.mf2();    //ok,调用Base::mf2
      d.mf3();    //ok,调用Derived::mf3
      d.mf3(x);   //bad,因为Derived::mf3遮掩了Base::mf3
    • //解决方法1
      //定义派生类
      class Derived : public Base {
      public:
          using Base::mf1;    //让基类中名为mf1和mf3的所有东西在此作用域内可见
          using Base::mf3;
          virtual void mf1();
          void mf3();
          void mf4();
          ...
      };
      
      //使用
      Derived d;
      int x;
      ...
      d.mf1();    //ok,调用Derived::mf1
      d.mf1(x);   //ok,调用Base::mf1
      d.mf2();    //ok,调用Base::mf2
      d.mf3();    //ok,调用Derived::mf3
      d.mf3(x);   //ok,调用Base::mf3
    • //解决方法2
      //定义基类
      class Base {
      public:
          virtual void mf1() = 0;
          virtual void mf1(int);
          ...
      };
      //定义派生类
      class Derived : public Base {
      public:
          virtual void mf1() { Base::mf1(); }  // 转交函数
          ...
      };
      
      //使用
      Derived d;
      int x;
      ...
      d.mf1();    //ok,调用Derived::mf1
      d.mf1(x);   //bad,因为Base::mf1被遮掩,且Base::mf1(int)没有被转交
  • 6.3 条款34:区分接口继承和实现继承

    • 如下代码所示,有3类继承关系:

      • 纯虚函数的继承,目的是让派生类只继承函数接口。派生类必须实现该接口。
      • 非纯虚函数的继承,目的是让派生类继承函数接口和缺省实现。派生类可以实现该接口,也可以选择使用基类的缺省实现。
      • 非虚函数的继承,目的是让派生类继承函数接口和强制实现。派生类不应该另外实现该接口,否则发生上一个条款所述的遮掩行为。
    • class Shape {
      public:
          virtual void draw() const = 0;
          virtual void error(const std::string& msg);
          int objectID() const;
          ...
      };
      
      class Rectangle : public Shape {...};
      class Ellipse : public Shape {...};
  • 6.4 条款35:考虑virtual函数之外的其他选择

    • //假设你正在写一个游戏软件,每个游戏人物都应该有"健康"这个属性
      class GameCharacter {
      public:
          virtual int healthValue() const;  //返回游戏人物的健康状态
          ...
      };
    • 上面的写法没有问题,基类提供了接口和一个缺省的实现,派生类可以选择是否重写这个函数。但是,作者也提醒,还有一些其它的写法:

      • //方法1:借由Non-Virtual Interface(NVI)手法实现Template Method模式
        class GameCharacter {
        public:
            int healthValue() const {
                ... //在开始主函数前,可以做一些额外的工作
                int retVal = doHealthValue();   //这个函数可以被子函数重写
                ... //在结束主函数后,可以做一些额外的工作
                return retVal;
            }
            ...
        private:
            virtual int doHealthValue() const {
                ...
            }
        };
      • //方法2:借由函数指针实现Strategy模式
        //这种方法允许同一个派生类的不同对象使用不同的函数计算健康状态
        class GameCharacter {
        public:
            typedef int (*HealthCalcFunc)(const GameCharacter&);
            explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
              : healthFunc(hcf) {
        
            }
            int healthValue const {
                return healthFunc(*this);
            }
            ...
        private:
            HealthCalcFunc healthFunc;
        };
      • //方法3:借由tr1::function完成Strategy模式
        //这种方法比函数指针更自由,它可以是函数指针,也可以是任何可以被调用的东西
        class GameCharacter {
        public:
            typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
            explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
              : healthFunc(hcf) {
        
            }
            int healthValue const {
                return healthFunc(*this);
            }
            ...
        private:
            HealthCalcFunc healthFunc;
        }
      • //方法4:古典的Strategy模式
        //HealthCalcFunc来自另一个继承体系
        class GameCharacter;
        class HealthCalcFunc {
        public:
            ...
            virtual int calc(const GameCharacter& gc) const {...}
            ...
        };
        HealthCalcFunc defaultHealthCalc;
        
        class GameCharacter {
        public:
            explicit GameCharacter(HealthCalcFun* phcf = &defaultHealthCalc)
                : pHealthCalc(phcf) {
        
            }
            int healthValue() const {
                return pHealthCalc->calc(*this);
            }
            ...
        private:
            HealthCalcFunc* pHealthCalc;
        };
  • 6.5 条款36:绝不重新定义继承而来的non-virtual函数

    • 我怀疑作者这条讲重复了,条款33讲"遮掩"的时候就讲到了这个内容
    • //基类
      class B {
      public:
          void mf();
          ...
      };
      //派生类
      class D {
      public:
          void mf();   //遮掩B::mf
          ...
      };
      //使用
      D x;
      B* pB = &d;   //调用B::mf()
      pB->mf();
      D* pD = &d;   //调用D::mf()
      pD->mf();
      //如果B::mf()是virtual函数,则上面两处都是调用D::mf()
      //所以也就可以解释,条款7所述的"使用多态时,基类的析构必须为virtual"
  • 6.6 条款37:绝不重新定义继承而来的缺省参数值

    • 条款36说了non-virtual不要重新定义,所以在派生类中能重新定义的是virtual函数。
    • virtual函数是动态绑定,而缺省参数值是静态绑定,这就会带来问题。
    • //基类
      class Shape {
      public:
           enum ShapeColor {Red, Green, Blue};
           virtual void draw(ShapeColor color=Red) const = 0;
      };
      //派生类
      class Rectangle : public Shape {
      public:
          virtual void draw(ShapeColor color=Green) const;
          ...
      };
      //使用
      Shape* pR = new Rectangle;  //请注意类型是Shape*
      pR->draw();   //调用Rectangle::draw(Shape::Red),因为缺省值是在编译期间静态绑定的,而pR的静态类型为Shape*,是基类
    • 解决方法是在条款35中找一种替代设计,比如NVI。
    • //基类
      class Shape {
      public:
          enum ShapeColor {Red, Green, Blue};
          void draw(ShapeColor color=Red) const {
              doDraw(color);
          }
          ...
      private:
          virtual void doDraw(ShapeColor color) const = 0;
      };
      //派生类
      class Rectangle : public Shape {
      public:
          ...
      private:
          virtual void doDraw(ShapeColor color) const {
              ...
          }
          ...
      };
  • 6.7 条款38:确保"复合"是has-a或is-implemented-in-terms-of关系

    • 复合(composition)是类之间的一种关系,不同于public继承,它代表has-a或"根据某物实现出"这样一种关系。
    • class Address {...};
      class PhoneNumber {...};
      class Person {
      public:
          ...
      private:
          std::string name;
          Address address;
          PhoneNumber voiceNumber;
          PhoneNumber faxNumber;
      };
  • 6.8 条款39:明智而审慎地使用private继承

    • private继承会将基类中继承而来的所有成员变为private成员。
    • 如下代码所示,private继承的关系更像是"复合"(Student类中带有Person类的一些功能),只有软件实现层面的意义,不具有设计层面上的意义。尽可能使用"复合"替代private继承。
    • class Person {...};
      class Student : private Person {...};
      void eat(const Person& p);
      
      Person p;
      Student s;
      eat(p);   //ok
      eat(s);   //bad,Student不能被视为Person
  • 6.9 条款40:明智而审慎地使用多重继承

    • 多重继承会发生"成员函数或成员变量重名"、"钻石型继承"等问题,对于这种情况建议使用virtual public继承,但这会带来空间和效率的损失。
    • 多重继承的复杂性,导致一般不会使用。只有virtual base classes(也就是继承多个接口)才最有实用价值,它不一定需要virtual public继承。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员修炼之道(影印版)

程序员修炼之道(影印版)

Andrew Hunt、David Thomas / 中国电力出版社 / 2003-8-1 / 39.00

本书直击编程陈地,穿过了软件开发中日益增长的规范和技术藩篱,对核心过程进行了审视——即根据需求,创建用户乐于接受的、可工作和易维护的代码。本书包含的内容从个人责任到职业发展,直至保持代码灵活和易于改编重用的架构技术。从本书中将学到防止软件变质、消除复制知识的陷阱、编写灵活、动态和易适应的代码、避免出现相同的设计、用契约、断言和异常对代码进行防护等内容。一起来看看 《程序员修炼之道(影印版)》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具