| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> Effective C++条款34:区分接口继承和实现继承 -> 正文阅读 |
|
[C++知识库]Effective C++条款34:区分接口继承和实现继承 |
Effective C++条款34:区分接口继承和实现继承(Differentiate between inheritance of interface and inheritance of implementation)《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读: 第6章:继承与面向对象设计 条款34:区分接口继承和实现继承??public继承由两部分组成:函数接口继承和函数实现继承。当我们设计类时,对于基类的成员函数可以大致做下面三种方式的处理:
??为了对这些不同的选择有一个更好的理解,考虑表示几何图形的类继承体:系:
1、纯虚函数??首先考虑纯虚函数draw:
??纯虚函数的两个最具特色的特征是:它们必须被继承它们的任何具象类重新声明;在抽象类中它们通常情况下没有定义。将这两个特征放在一起,你就会发现:
??这对Shape::draw函数是再合理不过的事了,因为所有的Shape对象来说都是能够画出的,这是一个合理的需要,但是Shape类不能为这个函数提供合理的缺省实现,比如,画一个椭圆的算法和画一个矩形的算法是不一样的。Shape::draw的声明对派生具现类的设计者说,“你必须提供一个draw函数,但是我并不知道你该如何实现它。” ??我们可以为一个纯虚函数提供一个定义。也就是你可以为Shape::draw提供一个实现,C++不会发出抱怨,但是调用它的唯一方式是在函数名前加上类名限定符:
??这项性质除了能给别人留下一个深刻的印象外,用途有限。 2、虚函数(非纯)??简单虚函数背后的故事同纯虚函数有些不太一样。通常,派生类继承函数接口,但是虚函数会提供一份实现代码,派生类可能覆写它。
??考虑Shape::error这个例子:
??这个接口表示,每个class都必须支持一个“当遇到错误时可调用的函数”,但是每个类对错误如何进行自由的处理。如果一个类不想做任何特殊的事情,那么调用基类Shape中error的默认实现就可以了。也就是Shape::error的声明对派生类的设计者说,“你可以支持error函数,但如果你不想自己实现,你可以使用Shape类中的默认版本。” ??先来看一个虚函数的演示案例,假设某航天公司设计一个飞机继承体系,该公司现在只有A型和B型两种飞机,代码如下:
??为了表示所有的飞机必须支持fly函数,还有不同型号的飞机可能需要fly的不同实现,因此Airplane::fly被声明为virtual。然而,为了避免在ModelA和ModelB中实现同一份代码,我们为Airplane::fly提供了默认实现,ModelA和ModelB可以同时继承。 ??这是典型的面向对象设计。两个类共享同一个特征(实现fly的方式),所以一般的特征都会移到基类中,然后被派生类继承。这种设计使得类的普通特性比较清晰,防止代码重复,可以促进将来的增强实现,使长期维护更加容易——这是面向对象如此受欢迎的原因。 ??现在假设XYZ公司界定购入新式C型飞机,型号C和型号A和B不一样,具体说是,它的飞行方式变了。 ??XYZ的程序员为Model C在继承体系中添加了新类,但是他们如此匆忙的添加新类,以至于忘了重新定义fly函数:
??然后代码中有这些动作:
??这会是一个灾难:型号C的飞机尝试用型号A或者型号B的飞行方式去飞行。这不是增加旅客信心的行为。 2.1 将默认实现分离成单独函数??问题不在于Airplane::fly有默认的行为,而在于允许 Model C在没有明确说明它需要基类行为的情况下继承了基类的行为。幸运的是,很容易为派生类提供只有在它们需要的情况下才为其提供的默认行为,这种技术在于切断“virtual函数”和其“默认实现”之间的连接。代码如下:
??注意,在A和B的类的fly()函数中,对defaultFly()函数做了一个inline调用(见条款30,inline和virtual函数之间的交互关系)
??现在C型飞机,或者别的添加的飞机就不会意外继承默认的飞行行为了(因为我们将默认的飞行行为封装到一个defualtFly函数中了),自己可以在fly中定义飞行行为了
??Airplane::defaultFly是一个非虚函数同样重要。因为没有派生类可以重定义这个函数,如果defaultFly是虚函数,就会有一个循环问题:万一某些派生类忘记重新定义defaultFly,会怎样? 2.2 利用纯虚函数提供默认实现??有人反对以不同的函数分别提供接口和缺省实现,像上面我们将fly()接口和实现(defaultFly()函数)分开来实现,有些人可能会反对这样做,因为这样会因过度雷同的函数名称而引起class命名空间污染。 ??如果不想将上述两个行为分开,那么可以为纯虚函数进行定义,在其中给出defaultFly()函数的相关内容。例如:
??这几乎和前一个设计一模一样,只不过在派生类的fly()函数中用纯虚函数Airplane::fly替换了独立函数Airplane::defaultFly。这种合并行为丧失了“让两个函数享有不同保护级别”的机会:例如上面的defaultFly()函数从protected变为了public(因为它在fly之中)。 3、普通成员函数(非虚)??最后,看看 Shape 的非虚函数objectID:
如果成员函数是个非虚函数:
你可以把Shape::objectID的声明想做是:
4、class设计者常犯的两个错误??“纯虚函数、非纯虚的virtual虚函数、非虚函数”之间的差异,使得指定你想要派生类继承的东西:只继承接口,或是继承接口和一份缺省实现,或是继承接口和一份强制实现。针对于不同的函数,经验不足的class设计者最常犯的两个错误如: 4.1 第一个错误??第一个错误是将所有函数声明为“non-virtual”,这使得派生类没有多余空间进行特化工作。 ??non-virtual析构函数尤其会带来问题(见条款7)。 ??当然,如果一个类不打算作为基类,那么将所有函数声明为“non-virtual”是可以的。但是如果该类会作为基类,那么可以适当的声明一些virtual函数(见条款7)。 ??如果你当心virtual函数的成本,那么可以参阅80-20法则(也可参阅条款30):
4.2 第二个错误??第二个错误是将所有成员函数声明为virtual。 ??有时候这样做是正确的,例如条款31的Interface classes。然而某些函数就是不该在派生类中被重新定义,因此你应该将那些函数声明为non-virtual的。 5、牢记
总结期待大家和我交流,留言或者私信,一起学习,一起进步! |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 0:04:36- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |