zoukankan      html  css  js  c++  java
  • C++11 final/override控制

    严正声明:如果没有搞明白重载、覆盖、隐藏等基础概念,请先参见随笔《重载 覆盖 隐藏

    【1】为什么要引入final关键字?

    在通常情况下,一旦在基类A中的成员函数fun被声明为virtual的,那么对于其派生类B而言,fun总是能够被重写的。

    有的时候我们并不想fun在派生类B中被重写,那么,C++98没有方法对此进行限制。如下示例:

     1 class MathObject
     2 { 
     3 public:
     4     virtual double Arith() = 0;
     5     virtual void Print() = 0;
     6 };
     7 
     8 class Printable : public MathObject
     9 { 
    10 public:
    11     double Arith() = 0;
    12     void Print()   // 在C++98中我们无法阻止该接口在其派生类被重写
    13     { 
    14         cout << "Output is: " << Arith() << endl;
    15     }
    16 };
    17 
    18 class Add2 : public Printable
    19 { 
    20 public: 
    21     Add2(double a, double b) : x(a), y(b) 
    22     {} 
    23 
    24     double Arith()
    25     {
    26         return x + y;
    27     } 
    28 private:
    29     double x, y;
    30 }; 
    31 
    32 class Mul3 : public Printable
    33 {
    34 public:
    35     Mul3(double a, double b, double c) : x(a), y(b), z(c)
    36     {}
    37 
    38     double Arith()
    39     { 
    40         return x * y * z;
    41     } 
    42 private: 
    43     double x, y, z;
    44 };

    基础类MathObject定义了两个接口:Arith和Print。

    类Printable则继承于MathObject并实现了Print接口。

    接下来,Add2和Mul3为了使用MathObject的接口和Printable类中Print的实现,于是都继承了Printable。

    这样的类派生结构,在面向对象的编程中非常典型。

    不过倘若这里的Printable和Add2是由两个程序员完成的,Printable的编写者不禁会有一些忧虑,如果Add2的编写者重载了Print函数,那么他所期望的统一风格的打印方式将不复存在。

    对于Java这种所有类型派生于单一元类型(Object)的语言来说,这种问题早就出现了。

    因此Java语言使用了final关键字来阻止函数继续重写。

    final关键字的作用是使派生类不允许再重写它所修饰的虚函数。

    C++11也采用了类似的做法,如下示例:

     1 struct Object
     2 { 
     3     virtual void fun() = 0;
     4 };
     5 
     6 struct Base : public Object
     7 {
     8     void fun() final; // 声明为final
     9 };
    10 
    11 struct Derived : public Base
    12 { 
    13     void fun();      // 无法通过编译
    14 };

     取Java的长处补自己的短处,这个机制得点个大赞。

    注意以上内容的言外之意:不可使用final修饰非虚函数。

    【2】为什么要引入关键字override?

    C++中的重写还有一个特点,就是对于基类声明为virtual的函数,之后的派生类版本都不需要再声明该函数为virtual。

    即使在派生类中声明了virtual,该关键字也是编译器可以忽略的。

    这带来了一些书写上的便利,却带来了一些阅读上的困难。

    比如上例中的Printable的Print函数,程序员无法从Printable的定义中看出Print是一个虚函数还是非虚函数。

    另外一点就是,在C++中有的虚函数会“跨层”,没有在父类中声明的接口有可能是祖先的虚函数接口。

    比如上例中,如果Printable不声明Arith函数,其接口在Add2和Mul3中依然是可重载的,这同样是在父类中无法读到的信息。

    这样一来,如果类的继承结构比较长(不断地派生)或者比较复杂(比如偶尔多重继承),派生类的编写者会遇到信息分散、难以阅读的问题(虽然有时候编辑器会进行提示,不过编辑器不是总是那么有效)。

    而自己是否在重载一个接口,以及自己重载的接口的名字是否有拼写错误等,都非常不容易检查。

    在C++11中为了帮助程序员写继承结构复杂的类型,引入了虚函数描述符override。

    如果派生类在虚函数声明时使用了override描述符,那么该函数必须重写其基类中的同名函数,否则代码将无法通过编译。

    如下示例:

     1 struct Base
     2 { 
     3     virtual void Turing() = 0;
     4     virtual void Dijkstra() = 0;
     5     virtual void VNeumann(int g) = 0;
     6     virtual void DKnuth() const;
     7     void Print();
     8 };
     9 
    10 struct DerivedMid : public Base
    11 {
    12 //  void VNeumann(double g);  // 接口被隔离了,曾想多一个版本的VNeumann函数
    13 };
    14 
    15 struct DerivedTop : public DerivedMid
    16 { 
    17     void Turing() override;
    18     void Dikjstra() override;          // 无法通过编译,拼写错误,并非要重写
    19     void VNeumann( double g) override; // 无法通过编译,参数不一致,并非要重写
    20     void DKnuth() override; // 无法通过编译,常量性不一致,并非要重写
    21     void Print() override;  // 无法通过编译,非虚函数不可使用override修饰
    22 };

    注释可以完美解释一切,不再赘述。

    个人认为,override关键字主要是为了弥补virtual关键字的不足。

    good good study, day day up.

    顺序 选择 循环 总结

  • 相关阅读:
    [redis] 普通 RedisPool 的 CRUD 实现
    [maven] 常用仓库地址
    [saiku] 通过 saiku 的 DEMO 分析 connection
    [backbone]backbone.js
    [saiku] JCR在saiku中的运用原理
    [redis] 分布式 Redis 的 CRUD 实现
    [redis] session 保存到 redis 简单实现
    [redis] redis 存取键值对常用的三种使用方式
    226. Invert Binary Tree
    225. Implement Stack using Queues
  • 原文地址:https://www.cnblogs.com/Braveliu/p/12230751.html
Copyright © 2011-2022 走看看