1  公有继承

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

如 Shape 类中,三个成员函数,对应三种继承方式:

class Shape {
public:
virtual void Draw() const = ; // 1) 纯虚函数
virtual void Error(const 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::Draw Shape *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 string& 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 string& destination) = ;
}; void Airplane::Fly(const string& destination)
{
// a pure virtual function default code for flying an airplane to the given destination
} class ModelA: public Airplane {
public:
virtual void Fly(const string& destination) { Airplane::Fly(destination); }
};

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

class ModelC: public Airplane {
public:
virtual void Fly(const string& destination);
}; void ModelC::Fly(const string& 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 Rectangle

Shape *pB = &rc; // get pointer to rc
pB->ObjectID(); // call ObjectID() through pointer Rectangle *pD = &rc; // get pointer to rc
pD->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 之 override的更多相关文章

  1. C++ 11 :override 关键字的使用

    override 关键字 作用:在成员函数声明或定义中, override 确保该函数为虚函数并覆写来自基类的虚函数. 位置:函数调用运算符之后,函数体或纯虚函数标识 "= 0" ...

  2. c++11之override关键字

    明确是用来 覆盖 用来对虚函数的  从新实现  加上后编译器就会帮我们去检查这个语法 总结就是:c++11允许派生类显示的注明它将使用那个成员函数改写基类的虚函数 https://blog.csdn. ...

  3. 浅析C#中new、override、virtual关键字的区别

    Virtual : virtual 关键字用于修饰方法.属性.索引器或事件声明,并使它们可以在派生类中被重写. 默认情况下,方法是非虚拟的.不能重写非虚方法. virtual 修饰符不能与 stati ...

  4. C++11--20分钟了解C++11 (下)

    20分钟了解C++11 9 override关键字 (虚函数使用) * * 避免在派生类中意外地生成新函数 */ // C++ 03 class Dog { virtual void A(int); ...

  5. 如何快速优化手游性能问题?从UGUI优化说起

    WeTest 导读   本文作者从自身多年的Unity项目UI开发及优化的经验出发,从UGUI,CPU,GPU以及unity特有资源等几个维度,介绍了unity手游性能优化的一些方法.   在之前的文 ...

  6. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  7. ASP.NET Core管道深度剖析(3):管道是如何处理HTTP请求的?

    我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但是就具体的实现来说,由于其中涉及很多对象的交互,我想很少人能够地把它弄清楚.为了让读者 ...

  8. ASP.NET Web API路由系统:Web Host下的URL路由

    ASP.NET Web API提供了一个独立于执行环境的抽象化的HTTP请求处理管道,而ASP.NET Web API自身的路由系统也不依赖于ASP.NET路由系统,所以它可以采用不同的寄宿方式运行于 ...

  9. ASP.NET Web API标准的“管道式”设计

    ASP.NET Web API的核心框架是一个消息处理管道,这个管道是一组HttpMessageHandler的有序组合.这是一个双工管道,请求消息从一端流入并依次经过所有HttpMessageHan ...

随机推荐

  1. .net c# 视频剪切抓取缩略图

    public string Cut(string ffmpegPath, string videoPath, string savePath, string imgSize, int sleepTim ...

  2. swift学习笔记之-属性

    //属性 import UIKit //属性(Properties)详解 /* 存储属性(Stored Properties):类.结构体中,不能在枚举中 计算属性(Computed Properti ...

  3. javascript 函数初探 (六)--- 闭包初探#4

    循环中的闭包: 让我们来看一下一个会循环三次的操作,她在每次迭代中都会创建一个返回当前序列号的新函数,该函数会被添加到一个数组中,并最终返回: function F(){ var arr = [], ...

  4. mysql不同版本和存储引擎选型的验证

    Mysql的版本和存储引擎较多,为了选择最适合业务使用的系统,需要进行一定的验证,本文描述mysql的验证过程和思路. 主要涉及: Mysql的版本 v Mariadb v Tokudb v Orac ...

  5. MySQL 安装 启动 基本语法概述

    MySQL 安装 启动 基本语法概述 MySQL安装和配置 我是直接使用安装包:mysql-installer-community-5.6.10.1.msi 安装的时候其中有几点要注意: 1.记住端口 ...

  6. 根据键盘调整textField(多个)位置使其不会被键盘挡住

    当一个界面上有个textField时,键盘出现时需要保证textField不会被键盘挡住. 一般的做法是,监听 UIKeyboardWillShowNotification和 UIKeyboardWi ...

  7. AFNetworking二次封装的那些事

    AFNetworking可是iOS网络开发的神器,大大简便了操作.不过网络可是重中之重,不能只会用AFNetworking.我觉得网络开发首先要懂基本的理论,例如tcp/ip,http协议,之后要了解 ...

  8. 【代码笔记】iOS-手机验证码

    一,效果图. 二,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController : UIVi ...

  9. 【代码笔记】iOS-对UIView进行截图

    一,效果图. 二,工程图. 三,代码. RootViewController.m #import "RootViewController.h" @interface RootVie ...

  10. 【Android】不依赖焦点和选中的TextView跑马灯【2】

    前言 之前有写一篇TextView跑马灯的效果,后来实际项目中有发现新的问题,比如还是无法自动跑,文本超过了显示区域就截取的问题,今天换了一种思路来实现,更简单更好用. 声明 欢迎转载,但请保留文章原 ...