C++进阶——浅谈隐式转化

在代码里我们或多或少都会依赖c++的隐式类型转换。

然而不幸的是隐式类型转换也是c++的一大坑点,稍不注意很容易写出各种奇妙的bug。

因此我梳理一遍c++的隐式类型转换

一、什么是隐式类型转换

概念:就是当你只有一个类型T1,但是当前表达式需要类型为T2的值,如果这时候T1自动转换为了T2,那么这就是隐式类型转换。

接下来看两个例子,首先是最常见的混用数值类型

int a = 0;
long b = a + 1; // int 转换为 long
if(a == b)
{// 默认的operator==需要a的类型和b相同,因此也发生转换
}

int转成long是向上转换,通常不会有太大问题,而long到int则很可能导致数据丢失,因此要尽量避免后者。

第二个例子是自定义类型到标量类型的转换

std::shared_ptr<int> ptr = func();
if(ptr) // 这里会从shared_ptr转换成bool
{ // 处理数据
}

因为提供了用户自定义的隐式类型转换规则,所以我们可以很简单地去判断智能指针是否为空。在这里if表达式里需要bool,因此ptr转换为了bool,这又被叫做语境转换。

由此可见隐式类型转换转换可以简化代码的书写。不过简化不是没有代价的,我们细细说来。

二、基础回顾

在正式介绍隐式类型转换之前,我们先要回顾一下基础知识,放轻松。

直接初始化

首先是类的直接初始化。

顾名思义,就是显式调用类型的构造函数进行初始化。举个例子:

structA{
A() = default;
A(constA&) = default;
A(int) {}
};
// 这是默认初始化: A a; 注意区分
A a1{}; // c++11的列表初始化
// 不能写出A a2(),因为这会被认为是函数声明
A a2(1);
A a3(a2); // 没错,显式调用复制构造函数也是直接初始化
autoa4 = static_cast<A>(1);

需要注意的是a4,用static_cast转换成类型T的这一步也是直接初始化。

这种初始化方式有什么用呢?直接初始化会考虑全部的构造函数,而不会忽略explicit修饰的构造函数。

显式地调用构造函数进行直接初始化实际上是显式类型转换的一种。

复制初始化

除去默认初始化和直接初始化,剩下的会导致复制的基本都是复制初始化,典型的如下:

A func(){
returnA{}; // 返回值会被复制初始化
}
A a5 = 1; // 先隐式转换,再复制初始化
voidfunc2(A a){} // 非引用的参数传递也会进行复制构造

然而类似A a6 = {1}的表达式却不是复制初始化,这是复制列表初始化,会直接选择合适的非explicit构造函数进行初始化,而不用创建临时量再进行复制。

复制初始化又起到什么作用呢?

首先想到的是这样可以创造某个对象的副本,没错,不过还有一个更重要的作用:

如果想要某个类型T1的value能进行到T2的隐式转换,两个类型必须满足这个表达式的调用T2 v2 = value

而这个形式的表达式正是复制初始化表达式。至于具体的原因,我们马上就会在下一节看到。

类型构造时的隐式转换

我们看一道经典的面试题:

std::strings = "hello c++";

请问创建了几个string呢?如果你脱口而出1个,那么面试官八成会狡黠一笑,让你回家等通知去了。

那么答案是什么呢?是1个或者2个。什么,你逗我呢?

先别急,我们分情况讨论。首先是c++11之前。

在c++11前题目里的表达式实际上会导致下面的行为:

  1. 首先"hello c++"const char[N]类型的,不过它在表达式中于是退化成const char *
  2. 然后因为s实际上是处于“声明即定义”的表达式中,因此适用的只有复制构造函数,而不是重载的=
  3. 因此等号的右半边必须也是string类型
  4. 因为正好有从const char *string的转换规则,因此把它转换成合适的类型
  5. 转换完会返回一个新的string的临时量,它会作为参数调用复制构造函数
  6. 复制构造函数调用完成后s也就创建完毕了。

在这里我们暂且忽略了string的写时复制等黑科技,整个过程创建了s和一个临时量,一共两个string。

很快c++11就出现了,同时还带来了移动语义,然而结果并没有改变:

  1. 前面步骤相同,字符串字面量隐式转换成string,创建了一个临时量
  2. 临时量是个右值,所以绑定给右值引用,因此移动构造函数被选择
  3. 临时量里的数据移动到s里,s创建完成

移动语义减少了不必要的内部数据的复制,但是临时量还是会被创建的。

有进捣鼓编译器的朋友可能要说了,编译器是不生成这个临时量的。是这样的,编译器会用复制省略(copy elision)优化这段代码。

是的,复制省略在c++11里就已经被提到了,不过那时候它是可选的,并不强制编译器支持这一优化。因此你在GCC和clang上观察到的不一定能代表全部的c++编译器的情况,所以我们仍以标准为基础推演了理论上的行为。

到目前为止答案都是2,然而很快有意思的事情发生了——复制省略在c++17里成为了被标准化的行为。

在c++17里除非必要,否则临时量(现在叫做右值的结果对象,一个右值只有在实际需要存在一个临时变量的情况下才会创建一个临时变量,这个过程叫做实质化,创建出来的那个临时量就是该右值的结果对象)不会被创建,换而言之,T obj = expr这样的形式会以expr产生结果直接调用合适的构造函数,而不会进行临时量的创建和复制构造函数的调用,不过为了保证语义的完整性,复制构造函数仍然被要求是可访问的,毕竟类本身不允许复制构造的话复制初始化本身就是不正确的,不能因为复制省略而导致错误的代码被编译通过。

所以现在过程变成了下面这样子:

  1. 编译器发现表达式是string的复制初始化
  2. 右侧是表达式会隐式转换产生一个string的纯右值用于初始化同一类型的s
  3. 判断复制构造函数是否可用,然后发现符合复制省略的条件
  4. 寻找string里是否有符合要求的构造函数
  5. 找到了string::string(const char *),于是直接调用
  6. s初始化完成

因此,在c++17下只会创建一个string对象,这比移动语义更加高效。这也是为什么我说题目的答案既可以是1也可以是2的原因。

同时我们还发现,在复制构造时的类型转换不管复制有没有被省略都是存在的,只不过换了一个形式,这就是我们后面要讲的内容。

总结:尽量不要去依赖隐式类型转换,多用explicit和各种显式转换,少想当然。

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

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

相关文章

详细分析McCabe环路复杂度(附例题)

目录 前言1. 基本知识2. 例题 前言 该知识点常出在408或者软考中&#xff0c;对此此文重点讲讲理论知识以及例题 对于例题平时看到也会更新 1. 基本知识 McCabe环路复杂度是一种用于衡量软件代码复杂性的指标&#xff0c;主要是通过计算代码中的控制流图中的环路数量来衡量…

机房——蓝桥杯十三届2022国赛大学B组真题

问题分析 这题用深搜广搜都能做&#xff0c;不过我更倾向于用广搜&#xff0c;因为广搜能更容易找到目标点。那么是采用结构体存储边还是采用二维数组存储临接矩阵呢&#xff1f;我们注意到n的取值范围为1e5,用二维数组哪怕是bool类型就需要至少1e10Byte的连续空间,这个空间太大…

【C++PCL】点云处理3D-Harris关键点提取

作者:迅卓科技 简介:本人从事过多项点云项目,并且负责的项目均已得到好评! 公众号:迅卓科技,一个可以让您可以学习点云的好地方 重点:每个模块都有参数如何调试的讲解,即调试某个参数对结果的影响是什么,大家有问题可以评论哈,如果文章有错误的地方,欢迎来指出错误的…

2022 年全国职业院校技能大赛高职组云计算赛项试卷(公有云)

#需要资源&#xff08;软件包及镜像&#xff09;或有问题的&#xff0c;可私聊博主&#xff01;&#xff01;&#xff01; #需要资源&#xff08;软件包及镜像&#xff09;或有问题的&#xff0c;可私聊博主&#xff01;&#xff01;&#xff01; #需要资源&#xff08;软件包…

5V升8.4V2A升压恒压WT3231

5V升8.4V2A升压恒压WT3231 WT3231 是一种高性能直流-直流&#xff08;DC-DC&#xff09;转换器&#xff0c;集成了能够承受10A电流和26mΩ低导通电阻的功率MOSFET。该转换器能提供高达12V的稳定输出电压&#xff0c;并具有固定600KHz开关频率&#xff0c;使得小型外部电感和电…

解决github无法克隆私有仓库,Repository not found问题(2024最新)

一、背景 这个问题出现&#xff0c;是你用了其他主机设备&#xff0c;需要重新clone私有库时&#xff0c;发现一直报找不到仓库&#xff0c;如下报错&#xff1a; remote: Repository not found.二、解决方法 &#xff08;1&#xff09;账号密码方式&#xff08;已不支持&am…

构建自己的docker镜像node.js

学习资源&#xff1a; 构建自己的 Docker 镜像_哔哩哔哩_bilibili 针对其中的一些比较困难的点写篇文章。 以下是对app.js的注释&#xff1a; // 使用 Koa 框架搭建 Node.js 应用的示例代码// 这两行代码引入了 koa 模块&#xff0c;并创建了一个新的 Koa 应用实例&#xf…

C++之QT文本处理QDir、QFileDialog、QStringList、QFile

一、相应的头文件 #include <QFileDialog> #include <QDir> #include <QStringList> 二、简介 1.QFileDialog 实际效果如下&#xff1a;比如需要选择打开的文件夹或者文件名&#xff0c;通过调用资源管理器的方式进行可视化操作。 代码示例为&#xff1a…

gitlab集群高可用架构拆分部署

目录 前言 负载均衡器准备 外部负载均衡器 内部负载均衡器 (可选)Consul服务 Postgresql拆分 1.准备postgresql集群 手动安装postgresql插件 2./etc/gitlab/gitlab.rb配置 3.生效配置文件 Redis拆分 1./etc/gitlab/gitlab.rb配置 2.生效配置文件 Gitaly拆分 1.…

Linux高级进程通信

目录 第1关&#xff1a;socket之本地通信 任务描述 相关知识 创建 socket 流程 命名 socket 其他接口 编程要求 答案&#xff1a; 第2关&#xff1a;命名管道 任务描述 相关知识 命名管道的定义 命名管道的创建 命名管道的访问 命名管道的访问 编程要求 答案&#xff1a; 第3关…

五月加仓比特币

作者&#xff1a;Arthur Hayes Co-Founder of 100x. 编译&#xff1a;Liam 编者注&#xff1a;本文略有删减 (以下内容仅代表作者个人观点&#xff0c;不应作为投资决策的依据&#xff0c;也不应被视为参与投资交易的建议或意见&#xff09;。 从四月中旬到现在&#xff0c;当你…

flask框架的初步认识

flask框架的初步认识 这是一个轻量级的网页框架&#xff0c;在运行后&#xff0c;就相当于服务器&#xff0c;当用户输入URL就会触发对应的事件调用方法&#xff0c;返回给用户一个网页文件&#xff0c;并通过自动识别html标签&#xff0c;来为用户呈现对应的样式和效果&#…

小红书达人置换合作推广怎么做?

小红书作为国内领先的生活方式分享平台&#xff0c;已成为品牌与消费者沟通的重要桥梁。达人置换合作推广&#xff0c;即品牌与小红书上的意见领袖&#xff08;KOL&#xff09;合作&#xff0c;通过他们的影响力推广产品&#xff0c;已成为品牌营销的重要手段。本文伯乐网络传媒…

wc文件统计功能 xargs network 静态IP

wc命令&#xff0c;word count&#xff0c;文件统计功能 # wc [选项] 文件名称 选项说明&#xff1a; -l : 统计总行数 -w : word&#xff0c;总单词数 -c : 统计总字节数 案例&#xff1a;统计/根目录下一共有多少个文件 # ls / | wc -l 案例&#xff1a;用户在计算机中有…

【光速上手 Hydra 】一行代码自动跑多次实验,Hydra 中的 Multirun 参数如何使用?

Hydra 是一个开源的 Python 框架&#xff0c;简化了研究和其他复杂应用的开发。其关键特性是能够通过组合动态地创建一个分层次的配置&#xff0c;并通过配置文件和命令行进行覆盖。Hydra 的名称来源于其能够运行多个类似的作业 - 就像一个有多个头的九头蛇一样。 主要特性&am…

TikTok 正式起诉美国政府;全新 iPad Pro 将搭载苹果 M4 芯片丨 RTE 开发者日报 Vol.199

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」&#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real Time Engagement&#xff09; 领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「…

Spring从零开始学使用系列(四)--Spring框架中的Bean作用域:Singleton与Prototype详解

1. 引言 在复杂的企业级应用开发中&#xff0c;理解和正确使用Spring框架的Bean作用域至关重要。Bean作用域决定了Bean的生命周期&#xff0c;即Bean的创建、存在期及销毁的时机。Spring通过灵活的作用域管理&#xff0c;为开发者提供了强大的工具来优化应用性能和资源利用。本…

机器学习 | 时间序列预测中的AR模型及应用

自回归模型&#xff0c;通常缩写为AR模型&#xff0c;是时间序列分析和预测中的一个基本概念。它们在金融、经济、气候科学等各个领域都有广泛的应用。在本文中&#xff0c;我们将探索自回归模型&#xff0c;它们如何工作&#xff0c;它们的类型和实际例子。 自回归模型 自回…

Rust读写CSV文件 一维Vec类型元素、二维Vec类型元素写入CSV文件

本文主要介绍Rust读写CSV文件方法&#xff0c; Vec类型元素基本操作方法&#xff0c;Rust把一维Vec类型元素、二维Vec类型元素写入CSV文件方法。 实例测试&#xff1a; 要求读“log.csv”文件数据&#xff0c;把“时间”列数据和“次数”列数据写入日志处理结果1.csv文件&…

五一 Llama 3 超级课堂 大完结

首先很感谢上海人工智能实验室和机智流等相关举办单位组织的这个活动&#xff0c;在Llama3发布不多时就让我们可以体验到大模型的进步&#xff0c;回顾整个活动&#xff0c;从内容上看是相当用心的。从A100的提供使用到大模型部署&#xff0c;微调&#xff0c;Agent功能应用和数…