C++ override及虚函数的讲解

来源:互联网 发布:全友家私 知乎 编辑:程序博客网 时间:2024/05/21 12:49

随笔 - 46  文章 - 0  评论 - 11 

C++11 之 override

1  公有继承

  公有继承包含两部分:一是 "接口" (interface),二是 "实现" (implementation)

  基类 Shape 中,三个成员函数,代表三种继承方式:

复制代码
class Shape {public:    virtual void Draw() const = 0;    // 1) 纯虚函数    virtual void Error(const std::string& msg);  // 2) 普通虚函数    int ObjectID() const;  // 3) 非虚函数};class Rectangle: public Shape { ... };class Ellipse: public Shape { ... };
复制代码

1.1  纯虚函数 (pure virtual)

  纯虚函数,表示继承的只是基类成员函数的接口,且要在派生类中重写该函数的实现

Shape *ps1 = new Rectangle;ps1->Draw(); // calls Rectangle::DrawShape *ps2 = new Ellipse;ps2->Draw(); // calls Ellipse::Draw

  若想调用基类的 Draw(),须加上 类作用域操作符 ::

ps1->Shape::Draw(); // calls Shape::draw

1.2  普通虚函数

  普通虚函数,对应在基类中定义一个缺省的实现 (default implementation),表示继承的是基类成员函数的接口缺省的实现,由派生类自行选择是否重写该函数

  实际上,允许普通虚函数同时继承接口和缺省实现是危险的 如下, ModelA 和 ModelB 是 Airplane 的两种飞机类型,且二者的飞行方式完全相同

class Airplane {public:    virtual void Fly(const Airport& destination);};class ModelA: public Airplane { ... };class ModelB: public Airplane { ... };

  这是典型的面向对象设计,两个类共享一个特性 -- Fly,则 Fly 可在基类中实现,并由两个派生类继承之

  现增加一个新的飞机型号 ModelC,其飞行方式与 ModelA,ModelB 并不相同,假如不小心忘了在 ModelC 中重写新的 Fly 函数

class ModelC: public Airplane {    ... // no fly function is declared};

  则调用 ModelC 中的 fly 函数,就是调用 Airplane::Fly,但是 ModelC 的飞行方式和缺省的并不相同

Airplane *pa = new ModelC;pa->Fly(Qingdao); // calls Airplane::fly!

  这就是前面所说的,普通虚函数同时继承接口和缺省实现是危险的最好是基类中实现缺省行为 (behavior),但只有在派生类要求时才提供该缺省行为

1.2.1  方法一 

  一种方法是 纯虚函数 + 缺省实现,因为是纯虚函数,所以只有接口被继承,其缺省的实现不会被继承。派生类要想使用该缺省的实现,必须显式的调用

复制代码
class Airplane {public:    virtual void Fly(const Airport& destination) = 0;};void Airplane::Fly(const Airport& destination){     // a pure virtual function default code for flying an airplane to the given destination}class ModelA: public Airplane {public:    virtual void Fly(const Airport& destination) { Airplane::Fly(destination); }};
复制代码

  这样在派生类 ModelC 中,即使一不小心忘记重写 Fly 函数,也不会调用 Airplane 的缺省实现

复制代码
class ModelC: public Airplane {public:    virtual void Fly(const Airport& destination);};void ModelC::Fly(const Airport& destination){    // code for flying a ModelC airplane to the given destination}
复制代码

1.2.2  方法二 

  可以看到,上面问题的关键就在于,一不小心在派生类 ModelC 中忘记重写 fly 函数,C++11 中使用关键字 override,可以避免这样的“一不小心”

1.3  非虚函数

  非虚成员函数没有 virtual 关键字,表示派生类不但继承了接口,而且继承了一个强制实现 (mandatory implementation)

  既然继承了一个强制的实现,则在派生类中,无须重新定义 (redefine) 继承自基类的成员函数,如下:

  使用指针调用 ObjectID 函数,则都是调用的 Shape::ObjectID()

复制代码
Rectangel rc; // rc is an object of type RectangleShape *pB = &rc; // get pointer to rcpB->ObjectID(); // call ObjectID() through pointerRectangle *pD = &rc; // get pointer to rcpD->ObjectID(); // call ObjectID() through pointer
复制代码

  如果在派生类中重新定义了继承自基类的成员函数 ObjectID 呢?

复制代码
class Rectangel : public Shape {public:    int ObjectID() const; // hides Shape::ObjectID};pB->ObjectID(); // calls Shape::ObjectID()pD->ObjectID(); // calls Rectagle::ObjectID()
复制代码

  此时,派生类中重新定义的成员函数会 “隐藏” (hide) 继承自基类的成员函数

  这是因为非虚函数是 “静态绑定” 的,pB 被声明的是 Shape* 类型的指针,则通过 pB 调用的非虚函数都是基类中的,既使 pB 指向的是派生类

  与“静态绑定”相对的是虚函数的“动态绑定”,即无论 pB 被声明为 Shape* 还是 Rectangle* 类型,其调用的虚函数取决于 pB 实际指向的对象类型

 

 2  重写 (override)

  在 1.2.2 中提到 override 关键字,可以避免派生类中忘记重写虚函数的错误

  下面以重写虚函数时,容易犯的四个错误为例,详细阐述之

复制代码
class Base {public:    virtual void mf1() const;    virtual void mf2(int x);    virtual void mf3() &;    void mf4() const;    // is not declared virtual in Base};class Derived: public Base {public:    virtual void mf1();        // declared const in Base, but not in Derived.    virtual void mf2(unsigned int x);    // takes an int in Base, but an unsigned int in Derived    virtual void mf3() &&;    // is lvalue-qualified in Base, but rvalue-qualified in Derived.    void mf4() const;        };
复制代码

  在派生类中,重写 (override) 继承自基类成员函数的实现 (implementation) 时,要满足如下条件:

  一虚:基类中,成员函数声明为虚拟的 (virtual)

  二容:基类和派生类中,成员函数的返回类型异常规格 (exception specification) 必须兼容

  四同:基类和派生类中,成员函数名形参类型常量属性 (constness) 和 引用限定符 (reference qualifier) 必须完全相同

  如此多的限制条件,导致了虚函数重写如上述代码,极容易因为一个不小心而出错

  C++11 中的 override 关键字,可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重写,则编译器会报错。

复制代码
class Derived: public Base {public:    virtual void mf1() override;    virtual void mf2(unsigned int x) override;    virtual void mf3() && override;    virtual void mf4() const override;};
复制代码

  这样,即使不小心漏写了虚函数重写的某个苛刻条件,也可以通过编译器的报错,快速改正错误

复制代码
class Derived: public Base {public:    virtual void mf1() const override;  // adding "virtual" is OK, but not necessary    virtual void mf2(int x) override;    void mf3() & override;    void mf4() const override; }; 
复制代码

 

小结:

1)  公有继承

  纯虚函数      => 继承的是:接口 (interface)

  普通虚函数   => 继承的是:接口 + 缺省实现 (default implementation)

  非虚成员函数 =>继承的是:接口 + 强制实现 (mandatory implementation)

2)  不要重新定义一个继承自基类的非虚函数 (never redefine an inherited non-virtual function)

3)  在声明需要重写的函数后,加关键字 override

 

参考资料:

 <Effective C++_3rd> item 34, item 36

 <Effective Modern C++> item 12

 

分类: 软件编程
标签: C++11
好文要顶 关注我 收藏该文  
胡马依北风
关注 - 4
粉丝 - 29
+加关注
2
0
« 上一篇:C++ 之 重载赋值操作符
» 下一篇:C++ 之 策略模式
posted on 2016-05-11 20:58 胡马依北风 阅读(7928) 评论(0) 编辑 收藏
刷新评论刷新页面返回顶部
【推荐】50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
【推荐】Vue.js 2.x 快速入门,大量高效实战示例
【活动】腾讯云 学生专属优惠套餐 多规格选择
葡萄城1114
最新IT新闻:
· ofo越过山丘
· 360重组议案过关股东大会500亿元资产注入在即
· 美国邮政推出VR应用 但并不是提供有关邮寄信件的信息
· 柳传志撰文:我为湖畔大学正名
· 趣店数据疑似外泄,十万可买百万学生信息
» 更多新闻...
阿里云1113
最新知识库文章:
· 大道至简,职场上做人做事做管理
· 关于编程,你的练习是不是有效的?
· 改善程序员生活质量的 3+10 习惯
· NASA的10条代码编写原则
· 为什么你参加了那么多培训,却依然表现平平?
» 更多知识库文章...