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

news2024/9/21 18:33:23

Effective C++条款24:若所有参数皆需类型转换,请为此采用non-member函数(Declare non-member functions when type conversions should apply to all parameters)

  • 条款24:若所有参数皆需类型转换,请为此采用non-member函数
    • 1、错误案例——将需要隐式类型转换的函数声明为成员函数
    • 2、混合运算错误原因分析
    • 3、以非成员函数版本代替成员函数版本
    • 4、牢记
  • 总结


《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:


条款24:若所有参数皆需类型转换,请为此采用non-member函数

1、错误案例——将需要隐式类型转换的函数声明为成员函数

  使类支持隐式转换是一个糟糕的想法。当然也有例外的情况,最常见的一个例子就是数值类型。

  举个例子,如果你设计一个表示有理数的类,允许从整型到有理数的隐式转换应该是合理的。在C++内置类型中,从int转换到double也是再合理不过的了(比从double转换到int更加合理)。看下面的例子:

class Rational
{
public:
    //构造函数未设置为explicit,因为我们希望一个int可以隐式转换为Rational
    Rational(int numerator = 0, int denominator = 1);
    int numerator()const;
    int denominator()const; 
    const Rational operator*(const Rational& rhs)const;
private:
    ...
};

  你想支持有理数的算术运算,比如加法,乘法等等,但是你不知道是由成员函数还是非成员函数,或者非成员友元函数来实现它们。你的直觉会告诉你当你犹豫不决的时候,你应该使用面向对象的精神。有理数的乘积和Rational类相关,就会很自然认为该在Rationl类内为有理数实现operator*。条款23曾反直觉的主张,将函数放进相关 class 内有时会与面向对象守则发生矛盾,现在我们将其放到一边,研究一下将operator*实现为Rational成员函数的做法:

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

  (如果你不明白为什么函数声明成上面的样子——返回一个const value值,参数为const引用,参考[条款3],条款20和条款21)

这个设计让你极为方便的执行有理数的乘法:

Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf*oneEighth;//正确
result = result*oneEighth;          //正确

  但是你不满足。你希望可以支持混合模式的操作,例如可以支持int类型和Rational类型之间的乘法。这种不同类型之间的乘法也是很自然的事情。

  当你尝试这种混合模式的运算的时候,你会发现只有一半的操作是对的:

Rational res = oneHalf * 2;//正确
Rational result = 2 * oneHalf; //错误

  那么哪里出错了呢???

2、混合运算错误原因分析

  将上面的例子用等价的函数形式写出来,你就会知道问题出在哪里:

result = oneHalf.operator*(2); // fine
result = 2.operator*(oneHalf ); // error!

  oneHalf对象是Rational类的一个实例,而Rational支持operator操作,所以编译器能调用这个函数。然而,整型2却没有关联的类,也就没有operator成员函数。编译器同时会去寻找非成员operator*函数(也就是命名空间或者全局范围内的函数):

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

  但是在这个例子中,没有带int和Rational类型参数的非成员函数,所以搜索会失败。

  再看一眼调用成功的那个函数。你会发现第二个参数是整型2,但是Rational::operator*使用Rational对象作为参数。这里发生了什么?为什么都是2,一个可以调用另一个却不能调用?

  是的,这里发生了隐式类型转换。编译器知道函数需要Rational类型,但你传递了int类型的实参,它们也同样知道通过调用Rational的构造函数,可以将你提供的int实参转换成一个Rational类型实参,这就是编译器所做的。编译器的做法就像下面这样调用:

const Rational temp(2); // 创建一个临时变量
result = oneHalf * temp; // 等同于oneHalf.operator*(temp);

  当然,编译器能这么做仅仅因为类提供了non-explicit构造函数。如果Rational类的构造函数是explicit的,下面的两个句子都会出错:

result = oneHalf * 2; // error! 在显示构造的情况下
result = 2 * oneHalf; // 一样的错误,一样的问题

  这样就不能支持混合模式的运算了,但是至少两个句子的行为现在一致了。

  然而你的目标是既能支持混合模式的运算又要满足一致性,也就是,你需要一个设计使得上面的两个句子都能通过编译。回到上面的例子,当Rational的构造函数是non-explicit的时候,为什么一个能编译通过另外一个不行呢?

  看上去是这样的,只有参数列表中的参数才有资格进行隐式类型转换。而调用成员函数的隐式参数,this指针指向的那个,绝没有资格进行隐式类型转换。这就是为什么第一个调用成功而第二个调用失败的原因。

3、以非成员函数版本代替成员函数版本

为什么设计非成员函数版本:

  从上面的“一”我们可以看到,如果operator*()为成员函数,在某些情况下即使存在隐式转换也不能成功执行

因此我们可以将成员函数版本改为非成员函数版本(见下面详细介绍)
例如下面将operator*()函数变为一个非成员函数。代码如下:

class Rational
{
public:
    Rational(int numerator = 0, int denominator = 1);
    int numerator()const;
    int denominator()const;
private:
   	...
};
 
const Rational operator*(const Rational& lhs,const Rational& rhs)
{
    return Rational(lhs.numerator()*rhs.numerator(),
        lhs.denominator()*rhs.denominator());
}
Rational oneFourth(1, 4);
Rational result;
result = oneFourth* 2;
result = 2 * oneFourth;

  这很nice,不过还有点要注意:operator* 是否该成为Rational class的一个友元函数呢?

  答案是否定的,因为 operator* 可以完全依靠Rational的public接口来实现。上面的代码就是一种实现方式。我们能得到一个很重要的结论:成员函数的反义词是非成员函数而不是友元函数。太多的C++程序员认为一个类中的函数如果不是一个成员函数(举个例子,需要为所有参数做类型转换),那么他就应该是一个友元函数。上面的例子表明这样的推理是有缺陷的。尽量避免使用友元函数,就像生活中的例子,朋友带来的麻烦可能比从它们身上得到的帮助要多。

在这里插入图片描述

4、牢记

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

总结

期待大家和我交流,留言或者私信,一起学习,一起进步!

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

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

相关文章

Swift基础语法 - 枚举

枚举的基本用法 定义:枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。 enum Direction {case northcase southcase eastcase west }enum Direction {case north,south,east,we…

数理统计笔记10:回归分析

引言 数理统计笔记的第10篇介绍了回归分析,从相关关系开始介绍,然后介绍回归分析,主要介绍了一元回归模型和多元回归模型,并对其中的原理和检验进行了叙述,最后简单介绍了一下可以化为线性回归模型的非线性回归模型。 …

【Gradle-5】Gradle常用命令与参数

1、前言 Gradle的命令有很多,熟悉常用命令之后,在日常开发中,不仅可以提升效率,也可以辅助我们快速定位并解决编译问题;而且某些情况下命令行(CLI)与按钮执行的编译结果是不一样的,比如构建时要传参(-P)&a…

大学生环保主题网页制作 环境网页设计模板 学生静态网页作业成品 dreamweaver保护地球环境HTML网站制作

🎀 精彩专栏推荐👇🏻👇🏻👇🏻 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业…

【电商项目实战】上传头像(详细篇)

🍁博客主页:👉不会压弯的小飞侠 ✨欢迎关注:👉点赞👍收藏⭐留言✒ ✨系列专栏:👉SpringBoot电商项目实战 ✨学习社区: 👉不会压弯的小飞侠 ✨知足上进&#x…

【Spring源码系列】Bean生命周期-实例化前

这里写目录标题前言一、实例化前 - InstantiationAwareBeanPostProcessor介绍InstantiationAwareBeanPostProcessor实例化前作用InstantiationAwareBeanPostProcessor实例化前代码案例二、实例化前 - 源码分析声明关键点源代码解读前言 在Bean的生命周期中,‘实例化…

Python还是很迷茫的小伙伴进来,教你用图秒懂Python

哈喽,大家好呀!今天为大家带来12张图解python,让你们轻松学会了解python。 1.Python 解释器: Python数据结构:变量与运算符:Python 流程控制:Python 文件处理:python 输入输出&…

(三)Vue之模板语法

文章目录插值语法指令语法Vue学习目录上一篇:(二)初识Vue 下一篇:(四)Vue之数据绑定 Vue模板语法有2大类: 1.插值语法2.指令语法 插值语法 功能:用于解析标签体内容。 写法&…

lombok入门

目录 lombok概述 lombok安装 Getter、Setter ToString EqualsAndHashCode NotNull 生成构造方法相关注解 Data、Builder Log Cleanup、SneakyThrows lombok概述 以前的Java项目中,充斥着太多不友好的代码:POJO的getter/setter/toString/构造方…

Python迭代器和生成器

在Python中,很多对象都是可以通过for语句来直接遍历的,例如list、string、dict等等,这些对象都可以被称为可迭代对象。至于说哪些对象是可以被迭代访问的,就要了解一下迭代器相关的知识了。 迭代器 迭代器对象要求支持迭代器协议…

cpu设计和实现(总结篇)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 学习cpu,主要还是因为自己对它的原理和实现还有很多不明白、不清楚的地方,本着追根溯源的精神,正好借助于veril…

项目接入腾讯云短信服务SMS实现向用户发送手机验证码

1、自述 早在18年的时候,我就在项目中使用过阿里云的短信服务,现在我上阿里云短信控制台看,还能看到当时创建的短信签名,如下图所示。 出于某种原因,我现在想重新申请一个新的签名,却审批失败了&#xf…

HashMap和Hashtable的详细区别

HashMap和Hashtable的详细区别 一、简述: 1.安全性 Hashtable是线程安全,HashMap是非线程安全。HashMap的性能会高于Hashtable,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.sy…

MyBatisPlus的使用入门

一、简介 官网:http://mp.baomidou.com/ 参考教程:http://mp.baomidou.com/guide/ MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 二、…

(ICLR-2019)DARTS:可微分架构搜索

DARTS:可微分架构搜索 paper题目:DARTS: DIFFERENTIABLE ARCHITECTURE SEARCH paper是CMU发表在ICLR 2019的工作 paper链接:地址 ABSTRACT 本文通过以可微分的方式制定任务来解决架构搜索的可扩展性挑战。与传统的在离散的、不可微分的搜索空…

【Android App】实战项目之使用OpenCV人脸识别实现找人功能(附源码和演示 超详细)

需要全部代码请点赞关注收藏后评论区留言私信~~~ 人脸识别自古有之,每当官府要捉拿某人时,便在城墙贴出通缉告示并附上那人的肖像。只是该办法依赖人们的回忆与主观判断,指认结果多有出入,算不上什么先进。 如今利用监控摄像头结合…

E3--FPGA实现LVDS收发实例和原理2022-12-03

1.什么是LVDS 一个新东西来的时候,人们总是希望能够宏观的定性的认识它。一个问题是,手机上用的“软件”该如何定义呢?来自百度百科的定义是,软件是指一系列按照特定顺序组织的计算机数据和指令的集合,如果你是非专业…

【Android App】给App集成WebRTC实现视频发送和接受实战(附源码和演示 超详细)

需要源码请点赞关注收藏后评论区留言私信~~~ 一、引入WebRTC开源库 WebRTC开源库的集成步骤如下: (1)给App模块的build.gradle添加WebRTC的依赖库配置; (2)App得申请录音和相机权限,还得申请…

[附源码]计算机毕业设计springboot自行车租赁管理系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

K-means聚类算法及Python代码实现

K-means聚类算法(事先数据并没有类别之分!所有的数据都是一样的) 1、概述 K-means算法是集简单和经典于一身的基于距离的聚类算法 采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。 该算法…