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. jQuery Label Better – 友好的表单输入框提示插件

    jQuery Label Better 帮助你标记您的表单输入域,带有美丽的动画效果而且不占用空间.这个插件的独特之处在于所有你需要做的就是添加一个占位符文本,只有当用户需要它的时候才显示标签. 您可 ...

  2. Numeral.js – 格式化和操作数字的 JavaScript 库

    Numeral.js 是一个用于格式化和操作数字的 JavaScript 库.数字可以格式化为货币,百分比,时间,甚至是小数,千位,和缩写格式,功能十分强大.支持包括中文在内的17种语言. 您可能感兴 ...

  3. iOS 正则 检测是否为手机号

    - (BOOL)validateMobile:(NSString *)mobileNum { NSString *regex = @"^1[3|5|7|8][0-9]\\d{8}$" ...

  4. 百度编辑器 UEditor 报错汇总

    1,编辑文本时使用js将需要编辑内容setContent到编辑器里,总是报(IE8及IE11):'this.body' 为空或不是对象,详细如下,其实报这样的错除多半是加载先后或加载不完全导致的: 消 ...

  5. iOS自定义字体

    1.下载字体库,如:DINCond-Bold.otf 2.双击,在mac上安装 3.把下载的字体库拖入工程中: 4.配置info.plist文件 5.xib方式设置自定义字体:Font选Custom, ...

  6. error=Error Domain=NSURLErrorDomain Code=-1003

    我的情况:模拟器 可以返回数据 .真机不可以.我手机连接的同事的共享,我以为他的网段和后台的网段在同一个网段.一直在找错误,打开手机网络设置一看  ,原来不在同一网段.手机的网络必须要跟PC的在同一个 ...

  7. iOS--XML三种解析方法( XMLDictionary)、(GDataXMLNode)、(NSXMLParser)

    iOS9之后,默认网络请求是https,所有我们要设置一下网络安全,具体设置如下 1.第三方类库 XMLDictionary 下载地址: https://github.com/nicklockwood ...

  8. 关于Android四大组件的学习总结

    Activity Android应用的用户界面是由Activity类管理的.和其他组件一样,Activity会用一系列生命周期回调函数通知当前的状态. 生命周期 Activity的四种状态 1.运行状 ...

  9. iOS 从Xcode看应用支持横竖屏

    要看一个应用是否支持横竖屏,要看Xcode里面的info.plist文件设置才清楚,每一个新建工程都会包含三个支持方式,即Supported interface orientations里面的就是 P ...

  10. git一些常用的操作(转载)

    译者序:这是一篇给像我这样的新手或者是熟悉图形工具的老鸟看的.仅作为快速入门的教程. git 现在的火爆程度非同一般,它被广泛地用在大型开源项目,团队开发,以及独立开发者,甚至学生之中. 初学者非常容 ...