Effective C++ 学习笔记 条款24 若所有参数皆需类型转换,请为此采用non-member函数

作者在导读中提过,令class支持隐式类型转换通常是个糟糕的主意。当然这条规则有其例外,最常见的例外是在建立数值类型时。假设你设计一个class用来表现有理数,允许整数“隐式转换”为有理数似乎颇为合理。的确,它并不比C++内置从int至double的转换来得不合理,而还比C++内置从double至int的转换来得合理些。假设你这样开始你的Rational class:

class Rational
{
public:// 构造函数刻意不为explicit,允许int-to-Rational隐式转换Rational(int numerator = 0, int denominator = 1);int numerator() const;    // 分子(numerator)和分母(denominator)的访问函数(accessor)——见条款22int denominator() const;private:// ...
};

你想支持算术运算诸如加法、乘法等等,但你不确定是否该由member函数、non-member函数,或可能的话由non-member friend函数来实现它们。你的直觉告诉你,当你犹豫就该保持面向对象精神。你知道有理数相乘和Rational class,因此很自然地似乎该在Rational class内为有理数实现operator*。条款23曾经反直觉地主张,将函数放进相关class内有时会与面向对象守则发生矛盾,但让我们先把那放在一旁,先研究一下将operator*写成Rational成员函数的写法:

class Rational
{
public:// ...const Rational operator*(const Rational &rhs) const;
};

如果你不确定为什么这个函数被声明为此种形式,也就是为什么它返回一个const by-value结果但接受一个reference-to-const实参,请参考条款3、20和21。

这个设计使你能够将两个有理数以最轻松自在的方式相乘:

Rational oneEight(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEight;    // 很好
result = result * oneEight;    // 很好

但你还不满足。你希望支持混合式运算,也就是拿Rational和例如int相乘。毕竟很少有什么东西会比两个数值相乘更自然的了——即使是两个不同类型的数值。

然而当你尝试混合式算术,你发现只有一半行得通:

result = oneHalf * 2;    // 很好
result = 2 * oneHalf;    // 错误!

这不是好兆头。乘法应该满足交换律,不是吗?

当你以对应的函数形式重写上述两个式子,问题所在便一目了然了:

result = oneHalf.operator*(2);    // 很好
result = 2.operator*(oneHalf);    // 错误!

是的,oneHalf是一个内含operator*函数的class的对象,所以编译器调用该函数。然而整数2并没有相应的class,也就没有operator*成员函数。编译器也会尝试寻找可被以下这般调用的non-member operator*(也就是在命名空间内或在global作用域内):

result = operator*(2, oneHalf);    // 错误!

但本例不存在这样一个接受int和Rational作为参数的non-member operator*,因此查找失败。

再次看看先前成功的那个调用。注意其第二参数是整数2,但Rational::operator*需要的实参却是个Rational对象。这里发生了什么事?为什么2在这里可被接受,在另个调用中却不被接受?

因为这里发生了所谓隐式类型转换(implicit type conversion)。编译器知道你在传递一个int,而函数需要的是Rational;但它也知道只要调用Rational构造函数并赋予你所提供的int,就可以变出一个适当的Rational来。于是它就那样做了。换句话说此一调用动作在编译器眼中有点像这样:

const Rational temp(2);    // 根据2建立一个暂时性的Rational对象
result = oneHalf * temp;    // 等同于oneHalf.operator*(temp);

当然,只因为涉及non-explicit构造函数,编译器才会这样做。如果Rational构造函数是explicit,以下语句没有一个可通过编译:

result = oneHalf * 2;    // 错误!(在explicit构造函数的情况下)无法将2转换为一个Rational
result = 2 * oneHalf;    // 一样的错误,一样的问题

这就很难让Rational class支持混合式算术运算了,不过至少上述两个句子的行为从此一致。

然而你的目标不仅在一致性,也要支持混合式算术运算,也就是希望有个设计能让以上语句通过编译。这把我们带回到上述两个语句,为什么即使Rational构造函数不是explicit,仍然只有一个可通过编译,另一个不可以:

result = oneHalf * 2;    // 没问题(在non-explicit构造函数的情况下)
result = 2 * oneHalf;    // 错误!(甚至在non-explicit构造函数的情况下)

结论是,只有当参数被列于参数列(parameter list)内,这个参数才是隐式类型转换的合格参与者。地位相当于“被调用之成员函数所隶属的那个对象”——即this对象——的那个隐喻参数,绝不是隐式转换的合格参与者(即oneHalf的operator*只有一个参数,它有一个隐喻参数,就是oneHalf对象自己,它自己是不能从2转换为Rational对象的,意思就是第一个参数是int时不能转换为Rational对象)。这就是为什么上述第一次调用可通过编译,第二次调用则否,因为第一次调用伴随一个放在参数列内的参数,第二次调用则否。

然而你一定也会想要支持混合式算术运算。可行之道终于拨云见日:让operator*成为一个non-member函数,俾允许编译器在每一个实参身上执行隐式类型转换:

class Rational
{// ...    不包括operator*
};const Rational operator*(const Rational &lhs, const Rational &rhs)    // 现在成了一个non-member函数
{return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}Rational oneFourth(1, 4);
Rational result;
result = oneFourth * 2;    // 没问题
result = 2 * oneFourth;    // 万岁,通过编译了!

这当然是个快乐的结局,不过还有一点必须操心:operator*是否应该成为Rational class的一个friend函数呢?

就本例而言答案是否定的,因为operator*可以完全藉由Rational的public接口完成任务,上面代码已表明此种做法。这导出一个重要的观察:member函数的反面是non-member函数,不是friend函数。太多C++程序员假设,如果一个“与某class相关”的函数不该成为一个member(也许由于其所有实参都需要类型转换,例如先前的Rational的operator*函数),就该是个friend。本例表明这样的理由过于牵强。无论何时如何你可以避免friend函数就该避免,因为就像真实世界一样,朋友带来的麻烦往往多过其价值。当然有时候friend有其正当性,但这个事实依然存在:不能够只因函数不该成为member,就自动让它成为friend。

本条款内含真理,但却不是全部的真理。当你从Object-Oriented C++跨进Template C++(见条款1)并让Rational成为一个class template而非class,又有一些需要考虑的新争议、新解法、以及一些令人惊讶的设计牵连。这些争议、解法和设计牵连形成了条款46。

请记住:
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。

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

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

相关文章

线上又出问题了!又是特殊场景,哎呀,当时怎么没有想到!

目录标题 导读01.为什么经常会发生测试场景覆盖不全的问题?02.如何提升测试覆盖度?03.综述 导读 在我们的测试工作中,是不是经常遇到这样的情形,发生了线上问题,产品、研发或者测试同学一拍脑袋:当时怎么没有想到,怎…

Vue.js+SpringBoot开发海南旅游景点推荐系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…

【论文阅读】VMamba:视觉状态空间模型

文章目录 VMamba:视觉状态空间模型摘要相关工作状态空间模型 方法准备状态空间模型离散化选择扫描机制 2D 选择扫描VMamba 模型整体结构VSS块 实验分析实验有效感受野输入尺度 总结 VMamba:视觉状态空间模型 摘要 受最近提出的状态空间模型启发,我们提出了视觉状态…

一款适合程序员开发复杂系统的通用平台——JNPF 开发平台

在过去,很多开发工具更侧重代码编辑,针对数据库增删改查(CRUD)类的 Web 系统开发,在界面设计、前后端数据交互等环节主要还是靠写代码,效率比较低。目前很多所谓的低代码开发平台,大多数也都是基…

Linux第75步_pinctrl子系统驱动和gpio子系统的常用函数

1、STM32MP1的pinctrl子系统驱动 pinctrl子系统源码目录为drivers/pinctrl&#xff0c;一个PIN最好只能被一个外设使用。 “stm32mp151.dtsi”中有一个“pin-controller节点标签”叫pinctrl pinctrl: pin-controller50002000 { #address-cells <1>; /*定义子节点的…

【24春招/Java】Java的市场情况介绍及Spring Boot基础

Java的市场情况 需求减少 人员增加 面试难度增加 1、Java高级开发&#xff0c;100份简历&#xff0c;筛选了20份&#xff0c;邀约20个人面试 面试机会&#xff1a;录取人数20:1 充分准备通过面试的前提 不要裸面&#xff01;&#xff01;&#xff01; 背葵花宝典&#xff08;…

23.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-实现配置工具数据结构

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;22.加载配置文件…

站库分离技术--反向代理技术-雷池云WAF-给自己搭建一个安全点的网站

文章目录 概要整体架构流程技术名词解释技术细节ssh-ubuntu服务器docker-映射-链接-通信nginx反代mysql设置数据库新密码 小结我的mysql映射目录我的wordpress映射目录 成果展示 概要 新买了一个云服务器&#xff0c;想搭建一个站库分离的wordpress为主的网站&#xff0c;采用d…

PID控制器组(完整SCL代码)

PID控制器组不是什么新概念,是在PID控制器的基础上,利用面向对象的思想对对象进行封装 批量实例化。 1、增量式PID https://rxxw-control.blog.csdn.net/article/details/124363197https://rxxw-control.blog.csdn.net/article/details/1243631972、完全增量式PID https:/…

Vue 3:引领前端开发的未来

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

自制STLink-V2.1

STLink-V2.1需要使用的芯片是STM32F103CBT6 原理图如下&#xff1a; PCB如下&#xff1a; 将板子焊接好后下载&#xff1a;STM32 DebugMass storageVCP V2.J40.M27的固件后&#xff0c;再使用ST官方ST-LinkUpgrade工具对我们自制的STLink-V2.1进行升级。升级完成后&#xff0c…

数据结构---复杂度(2)

1.斐波那契数列的时间复杂度问题 每一行分别是2^0---2^1---2^2-----2^3-------------------------------------------2^(n-2) 利用错位相减法&#xff0c;可以得到结果是&#xff0c;2^(n-1)-1,其实还是要减去右下角的灰色部分&#xff0c;我们可以拿简单的数字进行举例子&…

Redis 的基本全局命令

前言 Redis 常用的有 5 种数据结构&#xff0c;字符串&#xff0c;列表&#xff0c;哈希表&#xff0c;集合&#xff0c;有序集合&#xff0c;每一种数据结构都有自己独特的命令&#xff0c;但也有些通用的全局命令&#xff0c;本文所提到的是最基本的命令&#xff0c;Redis 的…

前端存储方案

如今主流的存储方案&#xff1a; cookieweb storageindexDB 这三个浏览器兼容性最高的三种前端储存方案 1、cookie 它的出现是为了解决 HTTP 协议无状态特性的问题&#xff0c;简单来说就是想要得到上次http请求的数据是办不到的&#xff0c;只有再次从新请求。我们见得最多…

Day 9. TCP并发模型、select、poll、epoll

TCP并发模型 1.TCP多线程模型&#xff1a; 缺点&#xff1a; 1&#xff09;创建线程会带来资源开销&#xff0c;能够实现 2.IO模型&#xff1a; 1&#xff09;阻塞IO&#xff1a;没有数据到来时&#xff0c;可以让任务故挂起&#xff0c;节省CPU资源开销&#xff0c;提高系…

牛客网KY15 abc

题目 描述&#xff1a; 设a、b、c均是0到9之间的数字&#xff0c;abc、bcc是两个三位数&#xff0c;且有&#xff1a;abcbcc532。求满足条件的所有a、b、c的值。 输入描述&#xff1a; 题目没有任何输入。 输出描述&#xff1a; 请输出所有满足题目条件的a、b、c的值。 a、b、c…

2024年零基础自学网络安全/Web安全,看这一篇就够了

作为一个安全从业人员&#xff0c;我自知web安全的概念太过于宽泛&#xff0c;我本人了解的也并不够精深&#xff0c;还需要继续学习。 但又不想新入行的人走弯路&#xff0c;所以今天随手写写关于web安全的内容&#xff0c;希望对初次遇到web安全问题的同学提供帮助&#xff…

【Leetcode】top 100 子串

560 和为k的子数组 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 思路&#xff1a;一次遍历元素&#xff0c;当当前累积和超过k时&#xff08;若当前元素>k&#xff0c;直接切换…

设计模式:软件开发的秘密武器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

HTML:注释的 5 种场景和 5 点注意事项

你好&#xff0c;我是云桃桃。 HTML 代码注释是用来在 HTML 源代码中添加一些说明性文字&#xff0c;而不会显示在页面中的内容。它们不会在浏览器中显示或渲染。 现在我们一起来看看它的语法&#xff0c;用途和注意事项吧。 注释语法 HTML 注释的基本语法格式是: <!--…