条款46:需要类型转换时请为模板定义非成员函数

1.前言

条款24讨论过为什么只有non-member函数才有能力“在所有实参身上实施隐式类型转换”,该条款并以Rational class的operator*函数为例operator*函数为例。而本条款的改变在于将Rational和operator*模板化了:

template<typename T>
class Rational{public:Rational(const T& numerator[=0,const T& denominator=1);const T numperator() const;const T denominator() const;....
};
template<typename T>
const Rational<T> operator* (const Rational<T>& lhs,const Rational<T>& rhs)
{...
}

2.实例分析

就像条款24,我们也希望支持混合式(Mixed-mode)算数运算,所以我们希望以下代码顺利通过编译,唯一不同的时Rationl和operator*如今都成了templates:

Rational<int> oneHalf(1,2);//该例子来自条款24,唯一不同的是Rational改为template
Rational<int> result=oneHalf*2;//错误,无法通过编译

上述失败给我们的启示是模板化的Rational内的某些东西和其non-template版本不同。在条款24内,编译器知道我们尝试调用什么函数(即接受两个Rationals参数的那个operator),但这里编译器不知道我们想要调用哪个函数。取而代之的是它们试图想出什么函数被名为operator*的template具现化出来。它们知道他们应该具现化某个“名为operator*”并接受两个Rational<T>参数”的函数,但为完成这一具现化行动,必须先算出T是什么。

为了推导T,它们看了看operator*调用动作中的实参类型。本例中那些类型分别为Ratioanl<int>(oneHalf的类型)和int(的类型)。

以oneHalf进行推导,operator*的第一参数被声明为Rational<T>,而传递给operator*的第一实参(oneHalf)的类型是Rational<T>,所以T一定是int,其它参数的推倒则没有这么顺利。operator*的第二参数被声明为Rational<T>,但传递给operator*的第二实参(2)类型是int。

只要利用一个事实,我们就可以缓和编译器在template实参推导方面受到的挑战:template class内的friend声明式可以指涉某个特定函数。哪意味着class Rational<T>可以声明operator*是它的一个friend函数。class template并不依赖template实参推导(后者只施行于function template身上),所以编译器总是能够在class Rational<T>具现化时得知T,因此,令Rational<T> class声明适当的operator*为其friend函数,可简化问题:

template<typename T>
class Rational{public:...friend const Rational operator*(const Rational& lhs,const Rational& rhs);
};template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs)
{...
}

现在对operator*混合式的调用可以通过编译了,因为当对象oneHalf被声明为一个Rational<int>,class Rational<int>于是被具现化出来,而作为过程的一部分,friend函数operator*(接受Rational<int>参数)也就被自动声明出来。后者身为一个函数而非函数模板(function template),因此编译器可在调用它时使用隐式转换函数(例如Rational的non-exolicit构造函数),而这便是混合式调用之所以成功的原因。

虽然这段代码通过了编译,但是却无法连接。首先我们对Rational内声明的operator*语法进行讨论。

在一个class template内,template名称可被用来作为"template和其参数"的简略表达式,所以在Rational<T>内我们可以只写Rational而不必写Rational<T>。本例中的operator*被声明为接受并返回Rational,如果它被声明如下,一样有效:

template<typename T>
class Rational{public:...friendconst Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs);....
};

然而使用简略的表达式比较轻松,也比较普遍。

现在回头想想出现的问题。混合式代码通过了编译,因为编译器知道我们要调用哪个函数(就是接受一个Rational<int>以及又一个Rational<int>的那个operator*),但那个函数只被声明于Rational内,并没有被定义出来,我们意图令此class外部的operator* template提供定义式,但是行不通-如果我们自己声明了一个函数(即Rational template内的作为),就有责任定义那个函数。既然我们没有提供定义式,连接器自然找不到它们。

或许最简单的办法就是将operator*函数本体合并至其声明式内:

template<typename T>
class Rational{public:....friend const Rational operator(const Rational& lhs,const Rational& rhs){return         Rational(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());}
}

这便如同我们所期望地正常运作起来:对operator*地混合式调用现在可编译连接并执行。

这项技术地有个有趣点是:虽然使用了friend,却与friend地传统用途“访问class的non-public成分”毫不相干,为了让类型转换可能发生于所有实参上面,我们需要一个non-member函数:为了令这个函数被自动具现化,我们需要将它声明在class内部;而在class内部声明non-member函数的唯一办法就是:令它成为一个friend。

正如条款30所说,定义于class内的函数都暗自成为Inline,包括像operator*这样的friend函数,可以将这样的inline声明所带来的冲击最小化,做法是令operator*不做任何事情,只调用一个定义于class外部的辅助函数。在本条款的例子中,这样做的意义不大,因为operator*已经是个单行函数,但对更复杂的函数而言,那么做也许就有价值。“令friend函数调用辅助函数”的做法的确值得研究。

“Rational是个template”这一事实意味着上述的辅助函数通常也是个template,所以定义lRational的头文件代码,比如:

template<typename T> class Rational;//声明Rational template
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs);
template<typename T>
class Rational{public:...friend         const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs){return  doMultiply(lhs,rhs);}...
};

许多编译器实质上会强迫你把所有template定义式放进头文件内,所以你或许需要在头文件内定义doMultiu\ply,比如以下例子:

template<typename T>
const Rational<T> domultiply(const Rational<T>& lhs,const Rational<T>& rhs)
{return Rational<T>(lhs.numerator*rhs.numerator(),lhs.denominator()*rhs.denominator());
}

作为一个template,doMultiply不支持所以混合式乘法,但它其实不需要。它只被operator*调用,而operator*支持了混合式操作,本质上operator*支持了类型转换所需的任何东西,确保两个Rational对象能够相乘,然后它将这两个对象传给一个适当的doMultiply template具现体,完成实际的乘法操作。

3.总结

当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所以参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。

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

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

相关文章

每日一题——LeetCode1304.和为零的N个不同整数

方法一 个人方法 n为偶数&#xff0c;只要同时放入一个数的正数和负数&#xff0c;那么和总为0&#xff0c;n是奇数就放入一个0&#xff0c;剩下的当偶数看待 var sumZero function(n) {let res[]if(n%2!0){res.push(0)n--}nn/2for(let i1;i<n;i){res.push(i)res.push(-i…

Sci Transl Med | 自体中和抗体在早期抗逆转录病毒治疗中增加

今天给同学们分享一篇实验文章“Autologous neutralizing antibodies increase with early antiretroviral therapy and shape HIV rebound after treatment interruption”&#xff0c;这篇文章发表在Sci Transl Med期刊上&#xff0c;影响因子为17.1。 结果解读&#xff1a; …

vue3自定义按钮点击变颜色实现(多选功能)

实现效果图&#xff1a; 默认选中第一个按钮&#xff0c;未选中按钮为粉色&#xff0c;点击时颜色变为红色 利用动态类名&#xff0c;当定义isChange数值和下标index相同时&#xff0c;赋予act类名&#xff0c;实现变色效果 <template><div class"page"&…

Elastic Search 8.12:让 Lucene 更快,让开发人员更快

作者&#xff1a;来自 Elastic Serena Chou, Aditya Tripathi Elastic Search 8.12 包含新的创新&#xff0c;可供开发人员直观地利用人工智能和机器学习模型&#xff0c;通过闪电般的快速性能和增强的相关性来提升搜索体验。 此版本的 Elastic 基于 Apache Lucene 9.9&#xf…

re:从0开始的HTML学习之路 2. HTML的标准结构说明

1. <DOCTYPE html> 文档声明&#xff0c;用于告诉浏览器&#xff0c;当前HTML文档采用的是什么版本。 必须写在当前HTML文档的首行&#xff08;可执行代码的首行&#xff09; HTML4的此标签与HTML5不同。 2. <html lang“en”> 根标签&#xff0c;整个HTML文档中…

如何本地部署虚VideoReTalking

环境&#xff1a; Win10专业版 VideoReTalking 问题描述&#xff1a; 如何本地部署虚VideoReTalking 解决方案&#xff1a; VideoReTalking是一个强大的开源AI对嘴型工具&#xff0c;它是我目前使用过的AI对嘴型工具中效果最好的一个&#xff01;它是由西安电子科技大学、…

数据结构学习1 初识泛型

装箱和拆箱 装箱/装包: 把一个基本数据类型转变为包装类型 拆箱/拆包: 把一个包装类型转变为一个基本数据类型 int a 1;Integer i a;// 自动装箱int b i;// 自动拆箱Integer ii Integer.valueOf(a);// 手动装箱&#xff0c;推荐使用 Integer.valueOf() 而不是 new Integer(…

安卓之导致ANR的原因分析,问题定位以及解决方案

一、引言 在Android应用开发中&#xff0c;Application Not Responding&#xff08;ANR&#xff09;是一种常见的性能问题&#xff0c;它直接关系到用户体验的质量。当应用在特定时间段内无法及时响应用户的交互或者系统事件时&#xff0c;系统将会抛出ANR错误&#xff0c;提示…

本地读取Excel文件并进行数据压缩传递到服务器

在项目开发过程中&#xff0c;读取excel文件&#xff0c;可能存在几百或几百万条数据内容&#xff0c;那么对于大型文件来说&#xff0c;我们应该如何思考对于大型文件的读取操作以及性能的注意事项。 类库&#xff1a;Papa Parse - Powerful CSV Parser for JavaScript 第一步…

html中,元素width和height的单位px、cm、mm、in、pc、pt、ch、em、rem、vh、vw、vmin、vmax的含义

在 HTML 中&#xff0c;元素的 width 和 height 属性可以使用多种单位来表示尺寸。下面是这些单位的含义&#xff1a; 像素&#xff08;px&#xff09;&#xff1a;像素是最常见的单位&#xff0c;表示固定的像素值。例如&#xff0c;width: 200px; 表示元素的宽度为 200 像素。…

springboot116基于java的教学辅助平台

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的基于java的教学辅助平台 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四…

要编译 Qt 的 .pro 工程,可以使用 qmake 和 make 工具

要编译 Qt 的 .pro 工程&#xff0c;可以使用 qmake 和 make 工具。下面是一个基本的 Makefile 示例&#xff0c;用于编译 Qt 的 .pro 工程&#xff1a; # 指定编译器 CXX g # 指定 qmake 命令的路径 QMAKE qmake# 指定目标文件名和可执行文件名 TARGET myapp# 定义源代码文…

R语言入门——多变量移除

目录 0、引言1、单变量删除的例子2、多变量移除2.1 ls的用法 2.2多变量删除 0、引言 有很多小伙伴在运行程序的时候就想在每次循环结束时提出一些占用内存大且无用的数据或变量&#xff0c;或者仅仅保留一些数据&#xff0c;但是rm的语法在删除多变量时候需要和ls()函数联用。…

Java中的深拷贝与浅拷贝

深拷贝与浅拷贝 深拷贝和浅拷贝是编程中常用的两种对象复制方式&#xff0c;它们在复制对象时处理对象内部引用的方式上有所不同。 浅拷贝 浅拷贝&#xff08;Shallow Copy&#xff09;只复制对象的顶层结构&#xff0c;而不复制对象内部的引用对象。换句话说&#xff0c;浅…

基于光口的以太网 udp 回环实验

文章目录 前言一、系统框架整体设计二、系统工程及 IP 创建三、UDP回环模块修改说明四、接口讲解五、顶层模块设计六、下载验证前言 本章实验我们通过网络调试助手发送数据给 FPGA,FPGA通过光口接收数据并将数据使用 UDP 协议发送给电脑。 提示:任何文章不要过度深思!万事万…

从白子画到东方青苍,你选择谁来守护你的修仙之旅?

从白子画到东方青苍,你选择谁来守护你的修仙之旅? 在繁花似锦的修仙世界中&#xff0c;每一位追梦者都渴望有那么一位守护者&#xff0c;与你共患难&#xff0c;共成长。热血与浪漫交织的《花千骨》与《苍兰诀》&#xff0c;给我们带来了两位风华绝代的守护者&#xff1a;白子…

磁盘初始化会丢失文件吗?答案揭晓!

“由于我的电脑出现了一些问题&#xff0c;我就将磁盘初始化了&#xff0c;但是里面还有很重要的文件&#xff0c;磁盘初始化了文件会丢失吗&#xff1f;有什么方法可以恢复丢失的文件呢&#xff1f;” 当我们谈论磁盘初始化&#xff0c;通常是指对硬盘或固态驱动器&#xff08…

解决执行npm(或pnpm)时报:证书过期 certificate has expired问题

项目执行 pnpm install 初始化时报 reason: certificate has expired 错误。 解决方案 1、取消ssl验证&#xff1a;npm config set strict-ssl false这个方法一般就可以解决了。2、更换npm镜像源&#xff1a;npm config set registry http://registry.cnpmjs.org npm config …

第13节-简历中的开放性问题

(点击即可收听) 不少公司的开放式题目每年不会有太大的变化&#xff0c;所以在答题前可先去相关求职论坛看看这些公司往年的问题&#xff0c;分析和思考自己应当怎么回答 开放式问题回答技巧 开放式问题主要考察的是求职者的求职动机、解决问题的能力、创造力等软实力&#xff…

initdb: command not found【PostgreSQL】

如果您遇到 “initdb: command not found” 错误&#xff0c;说明 initdb 命令未找到&#xff0c;该命令用于初始化新的 PostgreSQL 数据库群集。这通常是因为 PostgreSQL 相关的工具未正确安装或者安装路径不在系统的 PATH 变量中。 以下是解决这个问题的一些建议&#xff1a…