effective c++ 笔记 条款32-40

条款32:确定你的 public 继承塑模出 is-a 关系

public inheritance(公开继承)意味 “is-a”(是一种)的关系。子是父,父不是子,父具有一般性,子具有特殊性“public继承”意味is-a。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象。
要搞清楚子类是否必须拥有父类每一个特性,如果以生活经验判断,可能会出现错误
例1,企鹅是鸟,如果企鹅了继承了鸟,一般鸟的基类会默认能飞,但是企鹅不能飞。
例2,正方形是特殊的矩形,但是矩形拥有两个变量宽高,但是正方形只能有一个变量,因为语法层面没法保证两个变量永远相等。
在确定是否需要public继承的时候,我们首先要搞清楚子类是否必须拥有父类每一个特性,如果不是,则无论生活经验是什么,都不能视作”is-a”的关系。public继承关系不会使父类的特性或接口在子类中退化,只会使其扩充。

条款33:避免遮掩继承而来的名称

类似名称查找法则,继承体系中,当在子类使用一个名字时,编译器优先查找子类覆盖的作用域,未找到,再查找父类作用域,最后查找全局作用域

class Base {
public:void mf();void mf(double);
};class Derived : public Base {
public:void mf();
};

此时,子类的实例无法调用父类的重载函数,因为子类的mf覆盖了父类的mf
一种解决方式是,using关键字

class Derived : public Base {
public:using Base::mf;
};

会将父类中所有使用到名称mf的函数全部包含在子类中,包括其重载版本
如果不想要全部版本,只想要单一版本,尤其private继承时,使用转发函数

class Base {
public:virtual void mf();virtual void mf(double);
};class Derived : public Base {
public:virtual void mf() {Base::mf();}
};

条款34:区分接口继承和实现继承

不同类型的函数代表了父类对子类实现过程中不同的期望。

  1. 在父类中声明纯虚函数,是为了强制子类拥有一个接口,并强制子类提供一份实现。
  2. 在父类中声明虚函数,是为了强制子类拥有一个接口,并为其提供一份缺省实现。
  3. 在父类中声明非虚函数,是为了强制子类拥有一个接口以及规定好的实现,并不允许子类对其做任何更改(条款36要求我们不得覆写父类的非虚函数)
    普通虚函数可能出现的一个问题是,子类不适用父类的缺省实现,但如果忘记定制自己的实现。普通虚函数无法从代码层面提醒开发者。解决方式是,使用纯虚函数,并提供默认实现
class Airplane {
public:virtual void Fly() = 0;
};void Airplane::Fly() {// 缺省实现
}class Model : public Airplane { 
public:virtual void Fly() override {Airplane::Fly();}
};

从这里我们可以看出,将纯虚函数、虚函数区分开的并不是在父类有没有实现——纯虚函数也可以有实现,其二者本质区别在于父类对子类的要求不同,前者在于从编译层面提醒子类主动实现接口,后者则侧重于给予子类自由度对接口做个性化适配

条款35:考虑 virtual 函数以外的其他选择

  1. 藉由非虚接口手法实现 template method NVI:用public的非虚函数来调用private的虚函数具体实现,非虚函数必须为子类继承且不得更改
    注:C++ 允许 derived class 覆写 base class 的 private virtual 方法
    优点:可以在调用 private virtual 函数前后做一些额外的事情。调用之前可以做的工作:锁定互斥器,制造运转日志记录项,验证 class 约束条件,验证函数先决条件等等。调用之后可以做的工作:互斥器解除锁定,验证函数的事后条件,再次验证 class 约束条件等等
    缺点:某些class继承体系中,virtual函数必须调用其base class的版本,这就导致virtual函数必须是protected而不能是private,有些时候virtual函数甚至一定得是public
  2. 藉由函数指针实现 Strategy 模式:以对同一种子类对象赋予不同的函数实现,或者在运行时更改某对象对应的函数实现
    优点:同一类不同实例,可以用不同的实现函数,只要提供不同的初始化函数指针,叶可能通过添加set函数在运行期变更
    缺点:实现的函数只能访问public成分,如果需要non-public信息,就有问题。除非弱化封装,例如声明未friend,或者提供public访问函数
  3. 藉由 std::function 完成 Strategy 模式:允许包括函数指针在内的任何可调用物搭配一个兼容于需求的签名式。可以是函数对象,可以是某个成员函数,也可以用不同返回值的(只要能转换成需求的返回值类型)
// 使用返回值不同的函数
short CalcHealth(const GameCharacter&);
// 使用函数对象(仿函数)
GameCharacter chara1(CalcHealth);
struct HealthCalculator {int operator()(const GameCharacter&) const { ... }
};
GameCharacter chara2(HealthCalculator());
// 使用某个成员函数
class GameLevel {
public:float Health(const GameCharacter&) const;...
};
GameLevel currentLevel;
GameCharacter chara2(std::bind(&GameLevel::Health, currentLevel, std::placeholders::_1));
  1. 古典的 Strategy 模式:将虚函数也做成另一个继承体系类,然后添加一个指针来指向该继承体系的对象。容易辨认,想添加新的方法,添加子类即可。

条款 36:绝不重新定义继承而来的非虚函数

非虚函数执行的是静态绑定,由对象类型本身(静态类型,声明时的类型)决定调用的函数,编译时确定
虚函数执行的是动态绑定,决定因素不在对象本身,在于目标所指的对象类型(称之动态类型),运行期决定
虚函数的意思是“接口一定被继承,但实现可以在子类更改”,而非虚函数的意思是“接口和实现都必须被继承”

条款37:绝不重新定义继承而来的缺省参数值

条款 36 中我们已经否定了重新定义非虚函数的可能性,因此此处我们只讨论带有缺省参数值的虚函数
虚函数是动态绑定而来,但缺省参数值却是静态绑定
因此可能会在“调用一个定义于子类的虚函数”的同时,却使用父类为它所指定的缺省参数值(如果是父类指针/引用指向子类对象)
所以虚函数不要写缺省参数值,子类自然也不要改,虚函数要从始至终保持没有缺省参数值
可以使用条款35 NVI手法,让非虚函数外壳拥有缺省参数
绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。

条款38:通过复合塑膜出has-a关系,或“根据某物实现出”

两个类的关系除了继承,还有类的复合
public继承是is-a,复合意味着has-a(有一个)或 is-implemented-in-terms-of (根据某物实现出)
注意is-a与is-implemented-in-terms-of 区分
如果对象只是你的类的某个东西,这个对象属于应用域部分,是has-a。如果对象负责你的类的实现细节,是规则的制定者和执行者,那这个的对象属于实现域,是 is-implemented-in-terms-of
has-a:如 一个人可以拥有名字,地址

class Address;
class Person {
public:
private:std::string name;  //复合对象Address& Address;
};

is-implemented-in-terms-of:如用list实现一个set

template <typename T>
class Set {
public:bool Contains(const T& Item) const;//...
private:std::list<T> Rep;
};
template <typename T>
bool Set<T>::Contains(const T& Item) const {bool Result=std::find(Rep.begin(),Rep.end(),Item)!=Rep.end();return Result;
}

条款39:明智而审慎地使用private继承

private不是is-a关系

class Person{...};
class Student: private Person{...};
void eat(const Person& p);
void study(const Student& s);
Person p;
Student s;
eat(p);
eat(s); //error! public继承的话没问题,编译器会暗自转换类型
  1. 对于private继承,编译器不回子的将一个子类对象转成一个父类对象。
  2. private继承的所有父类成员,在子类中都会变成private属性

private 继承意味着:is-implemented-in-terms-of (根据某物实现出)。private 继承可以看作纯粹是为了实现细节,需要的不是类似 public 继承可以向外提供接口,仅仅是为了让derived class采用base class中已经具备的某种特性。derived和base之间并没有什么直接意义上的联系
当需要用一个类去实现另一个类,尽可能用复合,除非必要,不要采用private继承

  1. 当你的private子类继续派生时,可能想阻止重定义虚函数,类似final关键字,但因为private继承,无法实现(derived class可以重新定义private virtual函数,即使它们不能调用它,条款35),但如果是复合的形式将复合的类作为一个private成员,当前的子类继续派生时,可以防止复合类被继承后重新定义虚函数
  2. 降低编译依存性,如果是继承,子类被编译时,父类的定义必须可见。如果是复合的方式内含其他类的指针,只需要带一个简单的前置声明

当需要对工具类的某些方法(虚函数)做重载时,应选择private继承,这些方法一般都是工具类内专门为继承而设计的调用或回调接口,需要用户自行定制实现。
另一个极端案例是适用于你所处理的 class 不带任何数据时。这样的 class 不存在任何成员函数或变量

class Empty {};
class HoldsAnInt {
private:int x;Empty e;
};

此时 sizeof(HoldsAnInt) > sizeof(int) , 因为一个不含任何成员的class大小为1,编译器会默认放一个char。经过内存对其后,前置大小为8

class Empty {};class HoldsAnInt :private Empty{
private:int x;
};

此时 sizeof(HoldsAnInt) == sizeof(int) ,这种表现就是所谓的 EBO(empty base optimization)空白基类最优化

条款40:明智而审慎地使用多重继承

问题1:多重继承可能会引发歧义(ambiguity)行为。
解决办法:指明调用

mp.BorrowableItem::checkOut();
mp.ElectronicGadget::checkOut(); //报错,private函数

问题2:钻石型多重继承
解决办法:virtual继承
是否打算让棱形顶部的父类内的成员变量经过每一条路径被复制,如果不想要这样,应当使用虚继承,指出其愿意共享父类
但虚继承会在子类中额外存储信息来确认成员来自于哪个父类,会付出更多空间和速度的代价。虚基类的初始化责任是由继承体系中最底层的派生类负责,就导致了虚基类必须认知其虚基类并且承担虚基类的初始化责任
因此;非必要不使用虚继承。如果必须使用虚继承,尽可能避免在虚基类中放置数据

多重继承的确有正当用途。其中一个情节涉及“public 继承某个 Interface class” 和"private 继承某个协助实现的 class"

// IPerson 类指出要实现的接口
class IPerson {
public:virtual ~IPerson();virtual std::string Name() const = 0;virtual std::string BirthDate() const = 0;
};class DatabaseID {  ...  };// PersonInfo 类有若干已实现的函数
// 可用以实现 IPerson 接口
class PersonInfo {
public:explicit PersonInfo(DatabaseID pid);virtual ~PersonInfo();virtual const char* TheName() const;virtual const char* TheBirthDate() const;virtual const char* ValueDelimOpen() const;virtual const char* ValueDelimClose() const;...
};// CPerson 类使用多重继承
class CPerson: public IPerson, private PersonInfo {
public:explicit CPerson(DatabaseID pid): PersonInfo(pid) {}virtual std::string Name() const {       // 实现必要的 IPerson 成员函数return PersonInfo::TheName();  }virtual std::string BirthDate() const {  // 实现必要的 IPerson 成员函数return PersonInfo::TheBirthDate();  }
private:// 重新定义继承而来的虚函数const char* ValueDelimOpen() const {  return "";  }const char* ValueDelimClose() const {  return "";  }
};

PersonInfo刚好有若干函数可以帮助CPerson比较容易实现出来,IPerson是接口类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/727514.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

融资项目——通过OpenFeign在分布式微服务框架中实现微服务的远程调用

1.OpenFeign配置 首先&#xff0c;在需要调用其他的微服务的微服务中引入相关依赖。&#xff08;大多数项目中各微服务需要互相调用&#xff0c;可以直接在每个微服务中引入依赖&#xff09; <!--服务调用--><dependency><groupId>org.springframework.clou…

【Memory协议栈】NVRAM Manager 模块介绍

目录​​​​​​​ 前言 正文 1.功能简介 2.关键概念 3.功能详解 3.1 内存硬件抽象层Ea/Fee的寻址方案 3.2 基本存储对象Basic storage objects 3.2.1 NV Block 3.2.2 RAM Block 3.2.3 ROM Block 3.2.4 Administrative block 3.2.5 NV Block Header 3.3块管理类型…

JavaScript监听按键,禁止F12,禁止右键,禁止保存网页【Ctrl+s】等操作

禁止右键 document.oncontextmenu new Function("event.returnValuefalse;") //禁用右键禁止按键 其他键码&#xff1a;键码对照表 // 监听按键 document.onkeydown function () {// f12if (window.event && window.event.keyCode 123) {alert("F1…

云手机实现全方位的海外舆情监测

近年来&#xff0c;随着各大品牌在海外市场的崛起&#xff0c;海外舆情监测变得尤为重要。企业在拓展国际业务的同时&#xff0c;面临着海外市场的信息难获取、竞争激烈等问题。为解决这一难题&#xff0c;Ogcloud推出的云手机应运而生&#xff0c;为企业提供全方位的海外舆情监…

【软件测试】如何申请专利?

一、专利类型 在软件测试领域&#xff0c;可以申请发明专利、实用新型专利和外观设计专利。其中&#xff0c;发明专利是最常见的专利类型&#xff0c;它保护的是软件测试方法、系统和装置等技术方案。 二、申请专利的条件 申请专利需要满足新颖性、创造性和实用性三个条件。…

linux系统的运维有哪些工作,需要做好哪方面的知识储备?需要学些哪方面的技术?

Linux系统维护是一项复杂且重要的任务&#xff0c;涉及到多个方面的工作和知识储备。以下是一些主要的工作内容和所需的知识储备&#xff1a; 一、Linux系统维护的主要工作 系统安装与配置&#xff1a;包括选择合适的Linux发行版&#xff0c;通过光盘、USB或网络进行安装&…

PEIS源码 健康体检中心源码 C/S

目录 一、系统概述 二、系统开发环境 三、系统功能 检前管理 检中管理 检后管理 设备对接-PACS 设备对接-彩超 LIS-结果录入、审核、外送结果自动导入 一、系统概述 体检系统&#xff0c;是专为体检中心/医院体检科等体检机构&#xff0c;专门开发的全流程管理系…

HTML5:七天学会基础动画网页9

在进行接下来的了解之前我们先来看一下3d的xyz轴&#xff0c;下面图中中间的平面就相当于电脑屏幕&#xff0c;z轴上是一个近大远小的效果。 3d转换属性 transform 2D或3D转换 transform-origin 改变旋转点位置 transform-style 嵌套元素在3D空间如何显 …

JumpServer 简介安装

目录 1、概念介绍 JumpServer 概述 JumpServer 功能 JumpServer 组件 JumpServer 架构 2、前置安装 环境要求 安装 ELRepo 库 更新内核 设置 grub2 安装 Python 配置 Python 虚拟环境 3、安装 Jumpserver Core 组件 下载安装 替换客户端组件 安装 Python 依赖库…

十六、正则查找网址

描述 GG Bond最近正在研究网址&#xff0c;他发现好像很多网址的开头都是https://www&#xff0c;他想知道任意一个网址都是这样开头吗。于是牛牛向你输入一个网址&#xff08;字符串形式&#xff09;&#xff0c;你能使用正则函数re.match在起始位置帮他匹配一下有多少位是相…

【Web】浅浅地聊SnakeYaml反序列化两条常见利用链

目录 关于Yaml 关于SnakeYaml SnakeYaml反序列化利用 JdbcRowSetImpl链 ScriptEngineManager链 复现 基本原理 继续深入 关于Yaml 学过SpringBoot开发的师傅都知道&#xff0c;YAML和 Properties 文件都是常见的配置文件格式&#xff0c;用于存储键值对数据。 这里举…

CountDownLatch实现原理全面解析

简介 CountDownLatch是一个同步工具类&#xff0c;用来协调多个线程之间的同步&#xff08;即&#xff1a;用于线程之间的通信而不是互斥&#xff09;。它允许一个或多个线程进入等待状态&#xff0c;直到其他线程执行完毕后&#xff0c;这些等待的线程才继续执行。 CountDow…

干货分享③:免费制作产品管理系统!

他来了&#xff0c;他来了&#xff0c;他带着码上飞CodeFlying走来了&#xff01;今天继续为大家带来一期干货分享&#xff0c;教大家如何免费使用码上飞来的开发产品管理系统 &#xff01; 一、登陆官网 码上飞 CodeFlying | AI 智能软件开发平台&#xff01; 点击立即体验注…

Learn OpenGL 01

OpenGL的定义 一般它被认为是一个API(Application Programming Interface, 应用程序编程接口)&#xff0c;包含了一系列可以操作图形、图像的函数。然而&#xff0c;OpenGL本身并不是一个API&#xff0c;它仅仅是一个由Khronos组织制定并维护的规范(Specification)。 OpenGL规…

服务器严重不够啊

必需采购服务器了&#xff0c;

一个比较全面实用的C#帮助类、工具类库

前言 经常会有一些同学会问为什么感觉我身边的大佬写一个功能会这么快&#xff1f;一个类似的模块大佬可能半天就搞定了&#xff0c;而我要搞一两天。其实工作久了你会发现很多常用公共的帮助类和工具类&#xff0c;如常见的Excel数据导入导出、文件操作、字符串操作、数据转换…

SpringBoot源码解读与原理分析(一)SpringBoot整体概述

文章目录 第1章 SpringBoot整体概述1.1 Spring Framework1.1.1 Spring Framework的历史1.1.2 IOC与AOP 1.2 Spring Boot与Spring Framework1.3 Spring Boot的核心特性1.4 Spring Boot的体系 第1章 SpringBoot整体概述 Spring Framework 开发团队 支持不依赖外部容器的Web应用程…

从零搭建React18.2+ReactRoute6.22+TS5+RTK2.2搭配antd5+antd-style书写All in Js完整体验项目规范

1. 使用CRA创建项目 全局设置npm淘宝镜像源 npm config set registry https://registry.npmmirror.com -g使用最新版create-react-app初始化项目结构 npx create-react-app custom-template --template typescript初始化项目之后在package.json文件中配置使用node>18.0.0…

WordPress供求插件API文档:用户登录

该文档为WordPress供求插件文档&#xff0c;详情请查看 WordPress供求插件&#xff1a;一款专注于同城生活信息发布的插件-CSDN博客文章浏览阅读67次。WordPress供求插件&#xff1a;sliver-urban-life 是一款专注于提供同城生活信息发布与查看的插件&#xff0c;该插件可以实…

首次在Java8中,使用jni调用C的dll

这次是使用C语言生成的dll 以下是在Java 8中使用JNI调用DLL的步骤清单&#xff1a; 编写Java类接口&#xff1a;创建一个Java接口&#xff0c;定义与本地方法对应的方法签名。 public interface MyNativeInterface {void nativeMethod(); }编写Java类实现&#xff1a;创建一…