条款5:对定制的“类型转换函数”保持警觉

C++允许编译器在不同类型之间执行隐式转换(implicit conversions)。

继承了C的伟大传统,这个语言允许默默地将char转换为 int,将 short 转换为 doublea这便是为什么你可以将一个short 交给一个“期望获得double”的函数而仍能成功的原因。

C++还存在更令人害怕的转型(我指的是可能遗失信息的那种),包括将int转换为short,以及将double(或其他东西)转换为char。

你对这类转型无能为力,因为它们是语言提供的。然而当你自己的类型登场,你便有了更多的控制能力,因为你可以选择是否提供某些函数,供编译器拿来作为隐式类型转换之用。

两种函数允许编译器执行这样的转换:单自变量constructors 和隐式类型转换操作符。

所谓单自变量constructors 是指能够以单一自变量成功调用的constructors。如此的constructor 可能声明拥有单一参数,也可能声明拥有多个参数,并且除了第一参数之外都有默认值。下面是两个例子:

class Name
{
public:
Name(const string& s);//可以把string 转换为 Name。
//...
};class Rational {
public:Rational(int numerator =0,// 可以把int 转换为 Rational。int denominator = 1);
//...
};

所谓隐式类型转换操作符,是一个拥有奇怪名称的member function:关键词operator 之后加上一个类型名称。

你不能为此函数指定返回值类型,因为其返回值类型基本上已经表现于函数名称上。例如,为了让Rationalobjects 能够被隐式转换为doubles(这对掺杂有Rationa1 objects的混合型算术运算可能有用),你可能定义 class Rational 如下:

class Rational {
public:
operator double() const;// 将 Rational 转换为double。
//...
};

这个函数会在以下情况被自动调用:

Rational r(1,2);//r的值是1/2。double d= 0.5* r;
// 将 r转换为 double,
// 然后执行乘法运算。

或许这一切对你而言都只是复习。那很好,因为我真正要解释的是,为什么最好不要提供任何类型转换函数。

根本问题在于,在你从未打算也未预期的情况下,此类函数可能会被调用,而其结果可能是不正确、不直观的程序行为,很难调试。

让我们先处理隐式类型转换操作符,因为它比较容易掌握。假设你有一个class用来表现分数(rational numbers)。你希望像内建类型一样地输出 Rationalobjects内容。也就是说你希望能够这么做:

Rational r(1,2);
cout << r;//应该输出“1/2’

更进一步假设你忘了为Rational写一个operator<<,那么你或许会认为上述打印动作不会成功,因为没有适当的operator<<可以调用。

但是你错了。你的编译器面对上述动作,发现不存在任何operator<<可以接受一个Rational,但它会想尽各种办法(包括找出一系列可接受的隐式类型转换)让函数调用动作成功。

“可被接受的转换程序”定义十分复杂,但本例中你的编译器发现,只要调用Rational::operatordouble,将r隐式转换为double,调用动作便能成功。

于是上述代码将r以浮点数而非分数的形式输出。这虽然不至于造成灾难,却显示了隐式类型转换操作符的缺点:它们的出现可能导致错误(非预期)的函数被调用。

解决办法就是以功能对等的另一个函数取代类型转换操作符。为了允许将Rational 转换为 double,不妨以一个名为 asDouble 的函数取代 operator  double:

class Rational {
public:
//...
double asDouble() const;
};

如此的member function 必须被明确调用:

Rational r(1, 2);// 将 Rational转换为 double。cout << r;        //错误!Rationals没有operator<<。cout << r.asDouble();//可!以double 的形式输出r。

大部分时候,“必须明白调用类型转换函数”虽然带来些许不便,却可因为“不再默默调用那些其实并不打算调用的函数”而获得弥补。

一般而言,愈有经验的C++程序员愈可能避免使用类型转换操作符。

C++标准委员会中隶属标准程序库(见条款E49和条款35)小组的那些成员,应该算是最有经验的C++程序员了吧,这或许便是为什么标准程序库的string 类型并未含有“从 string object至C-style char*的隐式转换函数”的原因。他们提供的办法是用一个显式的member function a_str来执行上述转换行为。巧合吗?我想不是。

通过单自变量constructors完成的隐式转换,较难消除。此外,这些函数造成的问题在许多方面比隐式类型转换操作符的情况更不好对付。

举个例子,考虑一个针对数组结构而写的class template。这些数组允许用户指定索引值的上限和下限:

template<class T>
class Array 
{
public:
Array(int lowBound, int highBound);
Array(int size);
T& operator[](int index);
//...
};

上述class的第一个constructor 允许clients 指定某个范围的数组索引,例如10~20。身为一个双自变量constructor,此函数没有资格成为类型转换函数。

第二个constructor 允许用户只指定数组的元素个数,便可定义出 Array objects(这很类似内建数组)。它可以被用来作为一个类型转换函数,结果导致无尽苦恼。
例如,考虑一个用来对 Array<int>对象进行比较动作的函数,以及一小段代码:

bool operator==( const Array<int>& lhs,
const Array<int>& rhs);Array<int> a(10);
Array<int> b(10);
for (int i=0;i <10;++i)if (a==b[i])//哎哟!“a”应该是“a[i]”才对。
{
//do something for when
//a[i] and bli] are equal;
}
else{
//do something for when they're not;
}

我试图将a的每一个元素拿来和b的对应元素比较,但是当我键入a时却意外地遗漏了下标(方括号)语法。

我当然希望我的编译器发挥挑错功能,将它挑出来,但它却一声也不吭。

因为它看到的是一个 operator==函数调用,夹带着类型为 Array<int>的自变量a和类型为 int的自变量 b[i],虽然没有这样的operator==函数可被调用,我的编译器却注意到,只要调用 Array<int>constructor(需一个 int 作为自变量),它就可以将 int 转为 Array<int>object。于是它便放手去做,因而产生类似这样的代码:

for (int i = 0; i <10; ++i)
if (a == static_cast< Array<int> >(b[i]))

于是,循环的每一次迭代都拿a的内容来和一个大小为b[i]的临时数组(其内容想必未定义)做比较。此种行为不仅不令人满意,而且非常没有效率。因为每次走过这个循环,我们都必须产生和销毁一个临时的Array<int> object(见条款19)。

只要不声明隐式类型转换操作符,便可将它所带来的害处避免。

但是单自变量constructors却不那么容易去除,毕竟你可能真的需要提供一个单自变量constructors给你的客户使用。

与此同时,你也可能希望阻止编译器不分青红皂白地调用这样的constructors。

幸运的是有一种(事实上是两种)做法可以两者兼顾:一个是简易法,另一个可在你的编译器不支持简易法的情况下使用。

简易法是最新使用的C++特性:关键词explicit。这个特性之所以被导入,就是为了解决隐式类型转换带来的问题。其用法十分直接易懂,只要将constructors声明为 explicit,编译器便不能因隐式类型转换的需要而调用它们。不过显式类型转换仍是允许的:

template<class T>
class Array
{
public:
explicit Array(int size);//注意,使用“explicit”
//...
};
//...
Array<int> a(10);// 没问题,explicit ctors可以//像往常一样地作为对象构造之用。Array<int> b(10);// 也没问题。if (a ==b[i]).
//错误!无法将 int隐式转换
// 为 Array<int>。if (a == Array<int>(b[i]))
//没问题,将int转换为
// Array<int>是一种显式行为,
//(不过此行逻辑让人怀疑)。if (a == static_cast<Array<int> >(b[i]))
//一样地没问题,
//—样地让人怀疑。if (a ==(Array<int>)b[i]).
//c旧式转型也没问题,
// 但此行逻辑依旧令人质疑。

上述使用static_cast(见条款2)的那一行中,两个“>”字符之间的空格是有必要的。如果上述那行写成这样:

if (a == static_cast<Array<int>>(b[i]))..

它就有了不同的意义,因为C++编译器将“>>”视为单一的词元(token)。所以如果没有在“>”字符之间加上一个(一些)空格,上行语句会发生语法错误。

如果你的编译器尚不支持关键词explicit,你就只得走回头路,通过以下做法阻止单自变量constructors 成为隐式类型转换函数。

稍早我曾说过,关于“哪些隐式类型转换程序是合法的,哪些不是”,其间有着复杂的游戏规则。其中一条规则是:没有任何一个转换程序(sequence of conversions)可以内含一个以上的“用户定制转换行为(亦即单自变量constructor或隐式类型转换操作符)”。为了适当架构起你的classes,你可以利用这项规则,让你希望拥有的“对象构造行为”合法化,并让你不希望允许的隐式构造非法化。

让我们再次考虑Array template。你需要一种方法,不但允许以一个整数作为constructor 自变量来指定数组大小,又能阻止一个整数被隐式转换为一个临时性Array 对象。于是你首先产生一个新的class,名为 ArraySize。此型对象只有个目的:用来表现即将被产生的数组的大小。然后你修改 Array 的单白变量constructor,让它接收一个 ArraySize对象,而非一个int。代码如下:

template<class T>
class Array 
{
public:class ArraySize//这个class是新加入的。
{
public:
ArraySize(int numElements): theSize(numElements) ()
int size() const (return theSize;)
private:
int thesize;
};Array(int lowBound, int highBound);
Array(ArraySize size);// 注意这个新的声明。
//...
}

在这里,把 ArraySize 嵌套放进 Array内,强调一个事实:它永远与Array搭配使用。我们也把Arraysize 放在Array的public区,使任何人都能够使用它。好极了!

现在考虑当我们通过 Array 的“单自变量constructor”定义一个对象时,会发生什么事:

Array<int> a(10);

你的编译器被要求调用Array<int> class 中的一个自变量为int的constructor,但其实并不存在这样的constructor。

编译器知道它能够将 int 自变量转换为一个临时的 ArraySize 对象,而该对象正是 Array<int>constructor 需要的,所以编译器便依其所好执行了这样的转换。于是函数调用(以及随附的对象构造行为)得以成功。

“以一个 int 自变量构造起一个 Array 对象”这个事实依然可靠有效,但是除非“你希望避免的类型转换动作”确实被阻止,否则那也算不上是什么好消息。
是的,它们的确是被阻止了。再次考虑这段代码:

bool operator==(const Array<int>& lhs,
Array<int> a(10);
const Array<int>& rhs)
Array<int> b(10);
for (int i=0;i<10;++i)
if(a==b[i])
...
//哎哟!“a”应该是“a[i]”
//如今这形成了一个错误。

编译器需要一个类型为 Array<int>的对象在“==”的右手边,得以针对Array<int>对象调用 operator==,但是此刻并没有“单一自变量,类型为int”这样的constructor。

此外,编译器不能考虑将 int 转换为一个临时性的 ArraySize对象,然后再根据这个临时对象产生必要的 Array<int>对象,因为那将调用两个用户定制转换行为,一个将 int 转换为 Arraysize,另一个将Arraysize 转换为Array<int>。如此的转换程序是禁止的,所以编译器对以上代码发出错误消息。

本例对ArraySize class的运用,或许看起来像是特殊安排的情况,但它其实是更一般化技术的一个特别实例。

类似 Arraysize这样的classes,往往被称为proxy classes,因为它的每一个对象都是为了其他对象而存在的,好像其他对象的代理人(proxy)一般。

ArraySize对象只是“用来指定Array大小”的整数替身而已。Proxy objects 让你得以超越外观形式(本例为隐式类型转换),进而控制你的软件行为,是很值得学习的一项技术。如何学习?条款30便是一个开始。

然而在你进入proxy classes 主题之前,请再温习一下本条款的功课。是的,允许编译器执行隐式类型转换,害处将多过好处。所以不要提供转换函数,除非你确定你需要它们。
 

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

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

相关文章

linux下的调试工具gdb的详细使用介绍

在之前学习中我们使用的通常是集各种功能于一体的编译器&#xff0c;例如VS stdio&#xff0c;但是一个程序在编辑后还要进行编译&#xff0c;然后才能产生一个二进制的可执行文件&#xff0c;编辑和翻译工作都可以使用不同的软件进行&#xff0c;例如记事本就是一款编辑软件&a…

03.配置监控一台服务器主机

配置监控一台服务器主机 安装zabbix-agent rpm -ivh https://mirror.tuna.tsinghua.edu.cn/zabbix/zabbix/4.0/rhel/7/x86_64/zabbix-agent-4.0.11-1.el7.x86_64.rpm配置zabbix-agent,配置的IP地址是zabbix-server的地址&#xff0c;因为要监控这台主机 vim /etc/zabbix/zab…

android开发环境搭建步骤

搭建Android开发环境需要完成以下几个步骤&#xff1a; 下载Android Studio&#xff1a;访问Android官方网站下载最新版本的Android Studio安装包。安装Android Studio&#xff1a;运行下载的安装程序&#xff0c;按照提示完成安装过程。如果C盘空间足够&#xff0c;推荐安装在…

智能指针三剑客:shared_ptr的使用

目录 shared_ptr错误范例 其他补充 尺寸问题 移动语义 shared_ptr错误范例 裸指针&#xff1a; #include<iostream> using namespace std; void Test(shared_ptr<int> ps) {return; } int main() {int *pnew int(666);Test(p); //E0415 不存在从 "int *&…

免费开源线上线下交友社交圈子系统 小程序+APP+H5 可支持二开!

为什么要玩社交软件&#xff1a;互联网社交软件的独特优势 首先&#xff0c;社交软件为我们提供了一个便捷的沟通方式。在传统的交往方式中&#xff0c;人们需要面对面交流&#xff0c;这种方式在时间和空间上都受到限制。而社交软件打破了这些限制&#xff0c;无论我们身处何地…

既能自动仿写公众号爆文,还能批量帮你上架闲鱼商品,打造自己的数字员工,简直yyds

「想象一下&#xff0c;如果有一个机器人在你的计算机上24小时不间断地工作&#xff0c;会不会做梦都笑着」 一、RPA机器人是什么&#xff1f; RPA——机器人流程自动化&#xff0c;它可以帮助人们完成重复性的、繁琐的工作&#xff0c;比如数据输入、网页爬取、自动化流程等…

llama3 史上最强开源大模型,赶超GTP-4,逼宫OpenAI

2024年4月18日&#xff0c;Meta公司推出了开源大语言模型Llama系列的最新产品—Llama 3&#xff0c;包含了80亿参数的Llama 3 8B和700亿参数的Llama 3 70B两个版本。Meta称其为“迄今为止最强的开源大模型”。 怪兽级性能 LLaMA3 提供了不同参数规模的版本&#xff0c;以适应…

你真的知道Show Master Status吗?

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 你真的知道Show Master Status吗&#xff1f; 前言输出字段展示file详解Position详解Binlog_Do_DBBinlog_Ignore_DBExecuted_Gtid_Set 前言 在数据库的世界里&#xff0c;每一个字段都像是一个谜团&a…

微服务---gateway网关

目录 gateway作用 gateway使用 添加依赖 配置yml文件 自定义过滤器 nacos上的gateway的配置文件 我们现在知道了通过nacos注册服务&#xff0c;通过feign实现服务间接口的调用&#xff0c;那对于不同权限的用户访问同一个接口&#xff0c;我们怎么知道他是否具有访问的权…

Verilog中求两个数的差值

根据输入信号a,b的大小关系&#xff0c;求解两个数的差值&#xff1a;输入信号a,b为8bit位宽的无符号数。如果a>b&#xff0c;则输出a-b&#xff0c;如果a≤b&#xff0c;则输出b-a。 接口信号图如下&#xff1a; 代码如下&#xff1a; &#xff08;CSDN代码块不支持Veril…

WPF之绑定属性值转换

1&#xff0c;使用Binding.Format属性简易设置绑定的属性数据显示格式。 <TextBox Grid.Row"2" Grid.Column"1"><TextBox.Text><Binding Path"UnitCost" StringFormat"{}{0:C3}" > …

加州大学欧文分校英语中级语法专项课程02:Adjectives and Adjective Clauses 学习笔记

Adjectives and Adjective Clauses course certificate 本文是 https://www.coursera.org/learn/adjective-clauses 这门课的学习笔记。 文章目录 Adjectives and Adjective ClausesWeek 01: Adjectives and Adjective PhrasesLearning Objectives Adjectives Introduction Le…

Vue学习:21.mixins混入

在Vue中&#xff0c;mixins&#xff08;混入&#xff09;是一种用于分发Vue组件中可复用功能的灵活机制。它们允许你抽取组件中的共享功能&#xff0c;如数据、计算属性、方法、生命周期钩子等&#xff0c;并将其作为单独的模块复用到多个组件中。这种方式有助于保持代码的DRY&…

一个新细节,Go 1.17 将允许切片转换为数组指针!

在 Go 语言中&#xff0c;一个切片&#xff08;slice&#xff09;包含了对其支持数组的引用&#xff0c;无论这个数组是作为一个独立的变量存在于某个地方&#xff0c;还是仅仅是一个为支持分片而分配的匿名数组。 其切片基本结构都如下&#xff1a; // runtime/slice.go typ…

ChatGPT 4.0 直接用 !!!Code Copilot编程大模型、DALL-E AI绘图、绘制流程图、上传文件

嗨&#xff0c;你好呀&#xff0c;我是哪吒。 这一年最让人揪心的热点&#xff0c;就是各种层出不穷的AI技术。 原以为它只是短暂霸屏&#xff0c;但现实却赤裸裸展示了&#xff0c;什么叫AI抢走你的饭碗&#xff0c;连招呼都不打一声! 什么策划方案、公众号文案、营销卖点、…

学术咸鱼入门指南(1)

学术基础素养 一.巧用工具管理文献&#xff0c;形成自己的文献体系 养成习惯的第一步&#xff1a;文献命名 当你下载完一篇文献&#xff0c;应该做的第一步就是给文献重新命名。命名的逻辑可以随自己的喜好来&#xff0c;可以按照“发表年份作者文章标题”。 也可以使用文献…

ERROR - connection_lost: StreamLostError: (‘Transport indicated EOF‘,)

目录 python rabbitmq发送消息报错 网上的答案 heartbeat 重连效果不好 我的解决方法 亲测ok

JavaScript-DOM简介

JavaScript-DOM简介 之前我们说过JavaScript有三部分组成ECMAscript,BOM,DOM,之前我们都在了解JavaScript的语法即ECMAScript&#xff0c; 今天我们开始了解DOM(文档对象模型&#xff08;Document object Model&#xff09;&#xff0c;操作网页上的元素的API&#xff09; 什…

利用策略模式+模板方法实现项目中运维功能

前段时间项目中有个需求&#xff1a;实现某业务的运维功能&#xff0c;主要是对10张数据库表的增删改查&#xff0c;没有复杂的业务逻辑&#xff0c;只是满足运维人员的基本需要&#xff0c;方便他们快速分析定位问题。这里简单记录分享下实现方案&#xff0c;仅供参考。 一、…

企业遗失风景园林设计乙级资质证书要怎么补办

如果企业遗失了风景园林设计乙级资质证书&#xff0c;补办的基本流程可参考以下步骤&#xff0c;但请注意&#xff0c;实际操作应以所在地住房和城乡建设行政主管部门的最新要求为准&#xff0c;以下是一般流程&#xff1a; 遗失声明&#xff1a; 通常需要在省级以上公开发行的…