解析隐式类型转换操作operator double() const,带你了解隐式转换危害有多大

2023-11-09

前言

我首次看到这种函数的时候是在Flightgear飞行模拟器的源码中。再就是我今天在《More Effective C++》中条款五:对定制的“类型转换函数”保持警觉中看到的。这种函数叫类型转换类型,也是隐式类型转换操作符。

	operator double() const
	{
		return m_num;
	}

隐式类型转换操作符

用法:operator 类型名 [const]
注意:这个函数不能指定返回值类型。因为返回值类型在函数名上表现出来了。

我们看下面这个例子, 有三个隐式转换函数 都是将 Rational 转换为 double

class Rational
{
public:
	Rational(int numerator = 0, int denominator = 1)
	{
		if(0 == denominator)
		{
			m_num = 0;
			return;
		}
		m_num = static_cast<double>(numerator) / denominator;
	}
	double operator--()
	{
		return m_num -= 1;
	}
	double operator++()
	{
		return m_num += 1;
	}
	operator double() const
	{
		return m_num;
	}

private:
	double m_num;
};

int main()
{
	Rational r(1, 2);
	double d = 0.5 * r; // 将r转换为double 然后执行乘法运算。
	cout << d << endl;
	cout << ++d << "\t" << d << endl;
	cout << d++ << "\t" << d << endl;
	cout << --d << "\t" << d << endl;
	cout << d-- << "\t" << d << endl;

	return 0;
}

运行结果:
在这里插入图片描述

使用注意

仔细看下面这段代码,cout << r; 我们前面并没有重写operator<<函数。但是下面的代码可以正常运行。

int main()
{
	Rational r(1, 2);
	cout << r;
	return 0;
}

运行结果:
在这里插入图片描述
编译器在发现你没有写operator<<函数时,编译器会想尽办法去找一系列可接受的隐式类型转换 让函数执行起来。
这将会导致程序出现意想不到的结果。

解决方案

我们以功能对等的另一个函数取代类型转换操作符。我们可以自己写个asDouble的函数代替operator double()

看下面这段代码:

class Rational
{
public:
	Rational(int numerator = 0, int denominator = 1)
	{
		if(0 == denominator)
		{
			m_num = 0;
			return;
		}
		m_num = static_cast<double>(numerator) / denominator;
	}
	//operator double() const
	//{
	//	return m_num;
	//}

	double asDouble() const
	{
		return m_num;
	}

private:
	double m_num;
};

int main()
{
	Rational r(1, 2);
	cout << r;
	return 0;
}

运行结果:
可以看到程序已经报错了。
在这里插入图片描述
正常调用我们写的转换函数就可以正常实现功能。
在这里插入图片描述

深思

我们必须明白调用函数转换函数虽然不是很方便,但是可以避免“默认调用那些其实并不打算调用的函数”的错误。越有经验的程序员越要注意避免使用这种类型转换操作符。你要知道STL库中的string容器为什么不实现从string objectchar* 的隐式转换函数。而是使用c_str的成员函数吗?要知道他们都是非常老练的程序员了吧。

构造函数造成的隐式转换

单参数的构造函数也能实现隐式转换,而且难为发现。这可比隐式转换操作符难处理多了。
下面实现了一个模板数组类Array,并提供了俩种初始化方式、元素个数、下标运算符等。还实现了Array类的关系运算符(注:该函数仅仅为了测试效果,此处不谈函数功能是否正确)

template<class T>
class Array
{
public:
	Array(int lowBound, int highBound)
	{
		if(lowBound > highBound)
		{
			return;
		}
		m_arr.resize(highBound - lowBound);
		for (size_t i = lowBound; i <= highBound; ++i)
		{
			m_arr[i - lowBound] = i;
		}
	}
	Array(int size)
	{
		if(size > 0)
		{
			m_arr.resize(size);
		}
	}
	size_t size() const
	{
		return m_arr.size();
	}
	const T& operator[](size_t index) const
	{
		return m_arr[index];
	}

	T& operator[](size_t index)
	{
		return m_arr[index];
	}

private:
	vector<T> m_arr;
};

bool operator==(const Array<int>& lhs, const Array<int>& rhs)
{
	for (size_t i = 0; i < lhs.size() && i < rhs.size(); ++i)
	{
		if(lhs == rhs[i])
		{
			cout << "相同" << endl;
		}
		else
		{
			cout << "不相同" << endl;
			
		}
	}
	
	return true;
}

上述代码看着挺正常的吧,但是下面的测试代码和测试结果你绝对会吃惊。

int main()
{
	Array<int> arr1(2, 4);
	Array<int> arr2(5);
	for(size_t i = 0; i < arr1.size(); ++i)
	{
		cout << arr1[i] << "\t";
	}
	cout << endl;
	for (size_t i = 0; i < arr2.size(); ++i)
	{
		cout << arr2[i] << "\t";
	}
	cout << endl;

	arr1 == arr2;
	return 0;
}

在这里插入图片描述

arr1数组里面的元素是{2,3,4},arr2数组里面的元素是{0,0,0,0,0}。很明显没有一个元素是相同的,但事实就是打印了三次相同。 但是我想如果你仔细观察这个函数的比较表达式你就会发现,我少写了一个下表运算符。
在这里插入图片描述
没错,它应该是lhs[i] == rhs[i]这样的。但是当我意外的漏掉下表运算符时,编译器居然没有任何的抱怨????

如果你的编译器足够智能那么你会发现有这么一句提醒 使用构造函数array (int size) ,用户自定义将rhs[i]int 类型转换为’array<int> 类型
在这里插入图片描述

分析

编译器没有找到对于版本的关系运算符,但是编译器只需要调用Array<int>的构造函数(需要一个int变量),就可以将int转换为Array<int> 类型。所有就变成了lhs == Array<int>(rhs[i])。姑且抛开这个错误不谈,在这个循环中每一个的进行比较都会产生和释放一个临时的Array<int>对象 效率极低。

总结

虽然我们不声明隐式类型转换操作符,就可以避免一些不必要的一些麻烦,但是单变量的构造函数却防不胜防,因为你极有可能要为用户提供这个一个功能,但你又不想让编译器不分青红皂白的调用这个构造函数,

解决方案

explicit关键字

使用explicit关键字修饰 关闭函数的类型自动转换(防止隐式转换) 用法简单。

	explicit Array(int lowBound, int highBound)
	{
		if(lowBound > highBound)
		{
			return;
		}
		m_arr.resize(highBound - lowBound + 1);
		for (size_t i = lowBound; i <= highBound; ++i)
		{
			m_arr[i - lowBound] = i;
		}
	}
	explicit Array(int size)
	{
		if(size > 0)
		{
			m_arr.resize(size);
		}
	}

这样就会提示没有对应的关系运算符。
在这里插入图片描述

引入Proxy classes 代理类

为了避免int类型变量的隐式类型转换,我们可以将int类型的变量封装为一个proxy classes。用新类来保存要被产生数组的大小。

class ArraySize
	{
	public:
		ArraySize(size_t numElements) :m_size(numElements){}
		size_t size() const
		{
			return m_size;
		}

	private:
		size_t m_size;
	};

我们将这个proxy classes放到Array的public作用域下即可,然后我们更新单参数的构造函数。
在这里插入图片描述

同样编译器将会报错 提示没有对应的关系运算符。
在这里插入图片描述

总结

  1. 避免使用隐式类型转换操作符,使用转换函数代替。
  2. 避免使用单参数的构造函数,可以使用explicit关键字和proxy classes(代理类)。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解析隐式类型转换操作operator double() const,带你了解隐式转换危害有多大 的相关文章

  • 部署 MVC4 项目时出错:找不到文件或程序集

    过去 我只需使用 Visual Studio 2012 发布到 AWS 菜单项即可部署我的 MVC4 网站 到 AWS Elastic Beanstalk 现在 程序可以在本地编译并运行 但无法部署 从消息来看 它似乎正在寻找不在当前部署的
  • Signalr 在生产服务器中总是陷入长轮询

    当我在服务器中托管应用程序时 它会检查服务器端事件并始终回退到长轮询 服务器托管环境为Windows Server 2012 R1和IIS 7 5 无论如何 我们是否可以解决这个问题 https cloud githubuserconten
  • FFMPEG Seeking 带来音频伪影

    我正在使用 ffmpeg 实现音频解码器 在读取音频甚至搜索已经可以工作时 我无法找到一种在搜索后清除缓冲区的方法 因此当应用程序在搜索后立即开始读取音频时 我没有任何工件 avcodec flush buffers似乎对内部缓冲区没有任何
  • 为什么 POSIX 允许在只读模式下超出现有文件结尾 (fseek) 进行搜索

    为什么寻找文件结尾很有用 为什么 POSIX 让我们像示例中那样在以只读方式打开的文件中进行查找 c http en cppreference com w c io fseek http en cppreference com w c io
  • 将字符串从非托管代码传递到托管

    我在将字符串从非托管代码传递到托管代码时遇到问题 在我的非托管类中 非托管类 cpp 我有一个来自托管代码的函数指针 TESTCALLBACK FUNCTION testCbFunc TESTCALLBACK FUNCTION 接受一个字符
  • 使用 C# 在 WinRT 中获取可用磁盘空间

    DllImport kernel32 dll SetLastError true static extern bool GetDiskFreeSpaceEx string lpDirectoryName out ulong lpFreeBy
  • 写入和读取文本文件 - C# Windows 通用平台应用程序 Windows 10

    有用 但在显示任何内容之前 您必须在文本框中输入内容 我想那是因为我使用了 TextChanged 事件处理程序 如果我希望它在没有用户交互的情况下显示文本文件的内容 我应该使用哪个事件处理程序 因此 我想在按下按钮时将一些数据写入 C W
  • HttpClient 像浏览器一样请求

    当我通过 HttpClient 类调用网站 www livescore com 时 我总是收到错误 500 可能服务器阻止了来自 HttpClient 的请求 1 还有其他方法可以从网页获取html吗 2 如何设置标题来获取html内容 当
  • 编译的表达式树会泄漏吗?

    根据我的理解 JIT 代码在程序运行时永远不会从内存中释放 这是否意味着重复调用 Compile 表达式树上会泄漏内存吗 这意味着仅在静态构造函数中编译表达式树或以其他方式缓存它们 这可能不那么简单 正确的 他们可能是GCed Lambda
  • 如何在 Team Foundation 上强制发表有意义的签入评论?

    我有一个开发团队有一个坏习惯 他们写道poor签入评论 当我们必须在团队基础上查看文件的历史记录时 这使得它成为一场噩梦 我已经启用了变更集评论政策 这样他们甚至可以在签到时留下评论 否则他们不会 我们就团队的工作质量进行了一些讨论 他们很
  • 线程、进程和 Application.Exit()

    我的应用程序由主消息循环 GUI 和线程 Task Factory 组成 在线程中我调用一些第三方应用程序var p new Process 但是当我调用Application Exit 在消息循环中 我可以看到在线程中启动的进程仍在内存中
  • 可空属性与可空局部变量

    我对以下行为感到困惑Nullable types class TestClass public int value 0 TestClass test new TestClass Now Nullable GetUnderlyingType
  • AccessViolationException 未处理

    我正在尝试使用史蒂夫 桑德森的博客文章 http blog stevensanderson com 2010 01 28 editing a variable length list aspnet mvc 2 style 为了在我的 ASP
  • 什么是 C 语言的高效工作流程? - Makefile + bash脚本

    我正在开发我的第一个项目 该项目将跨越多个 C 文件 对于我的前几个练习程序 我只是在中编写了我的代码main c并使用编译gcc main c o main 当我学习时 这对我有用 现在 我正在独自开展一个更大的项目 我想继续自己进行编译
  • 作为字符串的动态属性名称

    使用 DocumentDB 创建新文档时 我想设置属性名称动态地 目前我设置SomeProperty 像这样 await client CreateDocumentAsync dbs db colls x new SomeProperty
  • 如何构建印度尼西亚电话号码正则表达式

    这些是一些印度尼西亚的电话号码 08xxxxxxxxx 至少包含 11 个字符长度 08xxxxxxxxxxx 始终以 08 开头 我发现这个很有用 Regex regex new Regex 08 0 9 0 9 0 9 0 9 0 9
  • 在Linux中使用C/C++获取机器序列号和CPU ID

    在Linux系统中如何获取机器序列号和CPU ID 示例代码受到高度赞赏 Here http lxr linux no linux v2 6 39 arch x86 include asm processor h L173Linux 内核似
  • 窗体最大化时自动缩放子控件

    有没有办法在最大化屏幕或更改分辨率时使 Windows 窗体上的所有内容自动缩放 我发现手动缩放它是正确的 但是当切换分辨率时我每次都必须更改它 this AutoScaleDimensions new System Drawing Siz
  • 如何在 C# 中播放在线资源中的 .mp3 文件?

    我的问题与此非常相似question https stackoverflow com questions 7556672 mp3 play from stream on c sharp 我有音乐网址 网址如http site com aud
  • 如何将字符串“07:35”(HH:MM) 转换为 TimeSpan

    我想知道是否有办法将 24 小时时间格式的字符串转换为 TimeSpan 现在我有一种 旧时尚风格 string stringTime 07 35 string values stringTime Split TimeSpan ts new

随机推荐

  • 三:Sensor SLPI层代码分析---

    三 Sensor SLPI层代码分析 在学习SLPI侧代码前我们先了解下SEE的registry config registry 放在 persist sensors registry registry中 它是通过config生成的 是给S
  • 循环遍历本地的图片使用BASE64编码,并在ajax也遍历图片

    前端调用ajax到后端去图片的方法 并返回 public void search HttpServletRequest request HttpServletResponse response throws Exception String
  • 【毕业设计】基于stm32的智能扫地机器人设计与实现 - 单片机 物联网

    文章目录 0 简介 1 课题背景 2 硬件系统总体框架 2 1 电机驱动 2 2 红外线传感器 2 3 超声波传感器 2 4 MPU6050 2 5 ATK ESP8266 WI FI 模块 2 6 电源管理模块 3 软件系统设计 3 1
  • 前端知识点

    写在前面 CSDN话题挑战赛第1期 活动详情地址 CSDN 参赛话题 前端面试宝典 话题描述 欢迎各位加入话题创作得小伙伴 如果我没有猜错得话 我觉得你是应该同我一样是一位前端人 如今前端在IT事业中的占比越来越重 已经成为不可缺少的部分
  • 2019年DNS服务器速度排行榜

    第一名 DNSPod 不得不说腾讯自从收购了DNSPod后 无论是服务还是速度都有显著的提升 无论是访问速度还是解析速度都在国内是处于龙头大哥的地位 昔日的老大114的地位已经不保 作为腾讯旗下的公司 在游戏解析这一块来说 技术自然是领先于
  • 排序算法详解(堆,归并,快速排序最简及理解写法)

    十大排序算法和复杂度 常见排序的详解 只讲解真实场景中常用的 简单的就不分析了大家稍微看一下就行 快速排序 快排的思想主要就是每次把一个位置放好后 可以把数组分成两半 递归处理子问题即可 空间复杂度OlogN 分析 每次都分成两半处理子问题
  • IDEA报错程序包xxx不存在,但Depandencies依赖里明明有

    IDEA报错程序包xxx不存在 但依赖里明明有 看一下这个项目的pom xml 我这边引用的是公共依赖 应该是运行的时候依赖没有引用过来 搞了半天 网上搜了很多没搜到 后来我把 settings gt Runner 设置调了一下 就没有问题
  • CUDA之Warp Shuffle详解

    之前我们有介绍shared Memory对于提高性能的好处 在CC3 0以上 支持了shuffle指令 允许thread直接读其他thread的寄存器值 只要两个thread在 同一个warp中 这种比通过shared Memory进行th
  • Zabbix 学习(六) 自动发现功能与主动监控的实现

    一 自动发现 当被监控的设备非常多的时候 手工添加将会变得非常不方便 可以使用自动发现功能 实现添加主机 添加到主机组 链接模板 自动发现流程 创建自动发现规则 创建动作 当主机被发现之后 执行什么操作 通过动作 添加主机 将模板应用到发现
  • qt 实现翻金币游戏

    游戏玩法介绍 游戏设置关卡二十关 通过选关界面可以选择进入到对应的关卡中 进入对应关卡之后 点击任意金币 可以使该硬币以及周边 上 下 左 右 金边翻转 如果硬币都翻转为金币 则游戏胜利 游戏界面设置 开始界面 开始场景中需要自定义一个按钮
  • MyBatis实现简单的增删查改操作(XML配置)

    最近在跟着B站传智的课程学习SSM 先学的是MyBatis 写一篇博客记录一下如何利用MyBatis实现简单的增删查改 主要是记录一下实现过程 实现思路 注意事项 避坑 本教程使用XML配置进行实现 一 软件环境 Java IDEA mys
  • Windows下,Hexo+GitHub搭建博客

    一 注册GitHub账号 二 创建GitHub仓库 创建git仓库时候 仓库的名称有格式要求 例如我的GitHub仓库用户名是thinkerwalker 那么我创建的仓库名称就是thinkerwalker github io 此处的警告是因
  • 2D人体姿态估计 - Convolutional Pose Machines(CPM)

    https github com namedBen Convolutional Pose Machines Pytorch https github com timctho convolutional pose machines tenso
  • QFileDialog打开文件夹,获得文件名(getOpenFileName,getExistingDirectory)

    1 QFileDialog getOpenFileName 示例 括号里的参数分别是 指定父类窗口部件 对话框使用的标题 默认打开后显示的目录 即告诉它从哪一级目录开始 右下角的文件过滤器 QString file name QFileDi
  • MongoDB复制集数据是如何复制的

    MongoDB 复制集 MongoDB复制集的主要意义在于实现服务高可用 类似于Redis中的哨兵模式 它主要提供两个方面的功能 1 数据写入主节点 Primary 时将数据复制到另一个副本节 Secondary 点上 2 主节点发生故障时
  • Android UI 之居中绘制文本内容的正确方法——实现自定义一个TextView

    原文地址 http blog csdn net carrey1989 article details 10399727 我们在自定义一个控件的时候 有时候会需要自己来绘制一些文本内容 这样就自然而然遇到确定文本的方位的问题 比如文本需要水平
  • 蓝桥杯-最少砝码(2021题)

    问题描述 你有一架天平 现在你要设计一套砝码 使得利用这些砝码可以称出任意 小于等于 NN 的正整数重量 那么这套砝码最少需要包含多少个砝码 注意砝码可以放在天平两边 输入格式 输入包含一个正整数 N 输出格式 输出一个整数代表答案 样例输
  • Java实现Excel导入导出操作详解

    前言 本次封装是基于 POI 的二次开发 最终使用只需要调用一个工具类中的方法 就能满足业务中绝大部门的导入和导出需求 1 功能测试 1 1 测试准备 在做测试前 我们需要將 2 环境准备 中的四个文件拷贝在工程里 如 我这里均放在了com
  • AD20笔记-PCB设计

    AD20笔记 文章目录 AD20笔记 PCB设计 新建PCB 导入原理图元器件 估计板子的大小 隐藏网络 机械层绘制放置区域 设置原点 设置板子大小 层叠管理器 正片负片 模块化分布 导入DXF文件 单独查看某一层 相连走线选择 精准移位吸
  • 解析隐式类型转换操作operator double() const,带你了解隐式转换危害有多大

    目录 前言 隐式类型转换操作符 使用注意 解决方案 深思 构造函数造成的隐式转换 分析 总结 解决方案 explicit关键字 引入Proxy classes 代理类 总结 前言 我首次看到这种函数的时候是在Flightgear飞行模拟器的