C++类对象的隐式类型转换和编译器返回值优化

文章目录

  • 前言
  • 1. 隐式类型转换
    • 1.1 单参数的隐式类型转换
    • 1.2 多参数的隐式类型转换
    • 1.3 explicit关键字
  • 2. 编译器的优化
    • 2.1 普通构造优化
    • 2.2 函数传参优化
    • 2.3 函数返回优化

前言

在类与对象的学习过程中,一定会对隐式类型转换这个词不陌生。对于内置类型而言,相似的类型会支持隐式类型转换,例如int a = 3.1;在这篇文章我们细细谈谈类对象中的隐式类型转换

  1. 类对象的隐式类型转换
  2. 编译器的优化

注意:这里的讨论,没有考虑右值

1. 隐式类型转换

关于隐式类型转换产生临时变量,我在类型转换细节中有谈到,大家可以先阅读一下。

1.1 单参数的隐式类型转换

C++11之前,C++98仅仅支持单参数的隐式类型转换,当然这种转换也带来了很多的便利!同时也有潜在危险,需要合理看待!

为了方便测试,我们在VS2022下,给出以下的A类:

#include<iostream>
using namespace std;
class A
{
public:A(int a = 1):_a(a){cout << "A(int a = 1)" << endl;}~A(){cout << "~A()" << endl;}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}//暂时不考虑移动构造和移动赋值int _a;
};

在大多数时候,我们会写出这样的代码, 例1.1.1:

int main()
{A a = 1; //直接以1来赋值这个a类对象return 0;
}

这个过程会发生什么呢?

  1. 编译器会先利用1来构造一个A类的tmp对象

  2. 调用a的对象的拷贝构造函数

(但似乎这样的代价也太大了,所以编译器会做出优化,我们稍后再谈)

当我们在使用STL的时候,也常常会发生这种隐式类型的转换,例如:

#include<iostream>
#include<string>
#include<vector>
using namespace std;int main()
{vector<string> v;v.push_back("this is a string?");return 0;
}

我们的vector存放的是string类对象,而我们传入的却是一个char*类型(字符串常量类型都被解释为了const char *)。在不考虑其它因素外,这个时候就应该会发生隐式类型的转换:将char*类型构造一个string对象,再调用push_back函数

这就是单参数的隐式类型转换

1.2 多参数的隐式类型转换

C++11支持了列表初始化
在这里插入图片描述
(C++11我们会在后面的专题谈到)

也对类的多参数的情况进行了升级!支持了多参数的隐式类型转换。给出下面一个例子:

#include<iostream>
using namespace std;
class A
{
public:A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)" << endl;}~A(){cout << "~A()" << endl;}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}int _a;int _b;
};

那么支持了多参数的隐式类型转换之后,我们可以这么写
例1.2.1:

int main()
{A a = {1, 3}; //支持的return 0;
}

同样地,我们应该了解到这个语句干了什么?

  1. 编译器会先利用{1, 3}来构造一个A类的tmp对象

  2. 调用a的对象的拷贝构造函数

1.3 explicit关键字

有些时候,我们其实并不想构造函数支持这种隐式类型转换,我们就可以采用关键字explicit来对构造函数进行声明!

语法如下:

class A
{
public:explicit A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)" << endl;}~A(){cout << "~A()" << endl;}int _a;int _b;
};int main()
{//关于这样的代码就无法通过编译了!//A a = {1, 3}; //不支持了return 0;
}

但是这样真的很方便……所以我们还可以采用另外一个形式,匿名对象

例1.3.1

int main()
{A a = A{ 1, 3 }; //语句一//A a = A({ 1, 3 }); //语句二return 0;
}

说明:

  1. 语句一可以在支持隐式类型转换的情况下使用。本质和隐式类型转换类似,都是构造一个tmp类对象。
  2. 语句二使用的前提是这个A类支持initializer_listA类构造函数。本质上是{ }调用了initializer_list的构造函数,是一个initializer_listtmp对象,然后再初始化A类。所以,当你的A类不支持这样的一个构造函数,就无法成功初始化了!

是否需要验证呢?

#include<iostream>
using namespace std;
class A
{
public:explicit A(int a = 1, int b = 2):_a(a), _b(b){cout << "A(int a = 1, int b = 2)" << endl;}~A(){cout << "~A()" << endl;}A(initializer_list<int> il) //支持列表初始化的构造函数{cout << "A(initializer_list<int> il)" << endl;}int _a;int _b;
};int main()
{A a = A({ 1, 3 }); //注意不要写成这样return 0;
}

在这里插入图片描述

这样的调用,不知道是否有说服力呢?

2. 编译器的优化

在上面的大多数例子中,我并没有验证那些我们看起来的步骤。因为:编译器是会对同一行的连续构造采取优化措施的

现在,在来考虑这个类,和几条语句:

2.1 普通构造优化

例2.1.1:

class A
{
public:A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)  " << _a << endl; //为了区别每一个构造,这里多给了一个打印}~A(){}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}int _a;int _b;
};int main()
{A a0(-1, -1);cout << " ------------------ " << endl;A a1 = a0; //语句一A a2 = { 0, 0 }; //语句二A a3 = A{ 1, 1 }; //语句三return 0;
}

来看运行结果:
在这里插入图片描述
(单参数的也是这样的结果)
在此之前,我们并没有给出实际的运行结果,因为编译器会为我们做出优化:

  • 语句一没有优化。a0本身就是一个存在的对象!
  • 语句二、三进行了优化,本来我们应该是先普通构造再拷贝构造,但编译器为我们直接构造

2.2 函数传参优化

同样直接给出示例:

class A
{
public:A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)  " << _a << endl; //为了区别每一个构造,这里多给了一个打印}~A(){}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}int _a;int _b;
};void func(A a) //注意这里并没有传引用 -- 传引用就不会进行拷贝构造了
{//……
}int main()
{A a0(-1, -1);cout << " ------------------ " << endl;func(a0); //语句一func({ 2, 2 }); //语句二func(A{ 3, 3 }); //语句三return 0;
}

在这里插入图片描述

同样发生了优化!

2.3 函数返回优化

这里的函数返回值优化又有所不同,返回值优化又被称为:RVO(Return Value Optimization)

给出示例:

class A
{
public:A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)  " << _a << endl; //为了区别每一个构造,这里多给了一个打印}~A(){}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}int _a;int _b;
};A func()
{A tmp(5,5);//……return tmp;
}int main()
{A a0(-1, -1);cout << " ------------------ " << endl;a0 = func(); //语句一A a1 = func(); //语句二return 0;
}

我们在func中创建一个临时变量tmp,想让这个tmp完成一些业务,然后返回这个临时变量。来看运行结果:
在这里插入图片描述
语句一:创建一个tmp对象,然后调用一个赋值运算符重载,十分合理的。但是看到语句二直接就完成了构造!。没有在func()中调用tmp的构造函数?还是没有调用a1的构造函数?

  1. 首先分析:A a1 = func();。首先函数返回的时候是会将返回值拷贝到一个tmp对象中的,然后再通过这个tmp对象返回给外面的接收变量,这里本身就有两个拷贝构造,编译器发生优化是很情理之中的!
  2. 同时函数func中又定义了一个变量,这个变量也会调用一个构造函数的。
  3. 可是结果告诉我们整个的调用只调用了一次构造函数

没错,这就是编译器的RVO

RVO编译器优化技术。它可以减少函数返回时创建临时对象的次数,从而提高程序的运行效率。RVO主要针对未命名的临时对象,消除了函数返回时创建的临时对象,避免了不必要的拷贝构造函数调用。

来看这样一张图片:
在这里插入图片描述

发现了吗?tmp这个对象被处理成为了一个指针!这个指针指向的对象就是a1对象。我们通过对tmp的操作,在编译器看来就是对a1进行操作。所以上述情况下只会调用一次拷贝构造函数

在有些时候,这样采用RVO的代码效率不差同时也更好维护!大家可以自己做性能测试!

RVO并不总是适用,存在一些限制条件,例如:

  • 函数抛出异常时,RVO可能不会进行。

  • 函数可能返回具有不同变量名的对象时,RVO无法进行。

  • 函数有多个出口时,RVO可能不会进行。

希望这篇文章能够帮助到你!

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

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

相关文章

领麦微红外温度传感器,摇奶器测温应用

在育儿领域&#xff0c;精准控制奶液温度是守护宝宝健康的重要环节。领麦微作为MEMS传感器领域的创新先锋&#xff0c;通过其红外测温传感器的非接触式测量、高精度测温、实时反馈以及智能温控节能等核心优势&#xff0c;为摇奶器注入了全新的智能化解决方案。这一技术不仅提升…

第十一届蓝桥杯 2020 C/C++组 蛇形填数

目录 题目&#xff1a; 题目描述: 题目链接&#xff1a; 思路&#xff1a; 思路详解&#xff1a; 代码&#xff1a; 代码详解&#xff1a; 题目&#xff1a; 题目描述: 题目链接&#xff1a; 蛇形填数 - 蓝桥云课 思路&#xff1a; 思路详解&#xff1a; 看图找规律…

如何检查 Watchtower 是否正常工作及更新未生效的排查方法【日常排错】

文章目录 前言一、验证 Watchtower 是否正在运行1. 检查 Watchtower 容器状态2. 查看 Watchtower 日志 二、检查5分钟间隔设置是否正确1. 确认启动命令2. 验证环境变量 三、排查更新未生效的原因1. 检查是否有镜像更新2. 检查容器标签3. 检查监控范围 四、测试 Watchtower 功能…

宝塔面板,删除项目后还能通过域名进行访问

场景&#xff1a;在阿里云宝塔面板中&#xff0c;删除了之前建立的html项目&#xff0c;通过之前绑定的域名还是可以访问&#xff0c;又把项目的目录文件删除&#xff0c;发现还是不行 又清理了浏览器缓存&#xff0c;但还是有这个问题通过该域名重新创建一个html项目&#xff…

多层PCB SMT贴装全流程指南:从物料准备到回流焊工艺控制

在电子制造领域&#xff0c;多层PCB板元器件贴片是一项重要的技术操作。本文将详细介绍多层PCB板元器件贴片的操作流程和注意事项&#xff0c;帮助您更好地理解和掌握这项技术。 一、准备阶段 在进行多层PCB板元器件贴片操作前&#xff0c;需要做好以下准备工作&#xff1a; 1.…

PAT(最近)

1022 D进制的AB - PAT (Basic Level) Practice &#xff08;中文&#xff09; 加减位置调换 本来以为就是简单的 十进制转换为一个长的字符串 没想到在那个拼接字符串的时候 只需要简单的 加减位置调换就可以 避免使用麻烦的翻转函数 import java.util.Scanner; public clas…

【Harbor v2.13.0 详细安装步骤 安装证书启用 HTTPS】

Harbor v2.13.0 详细安装步骤&#xff08;启用 HTTPS&#xff09; 1. 环境准备 系统要求&#xff1a;至少 4GB 内存&#xff0c;100GB 磁盘空间。 已安装组件&#xff1a; Docker&#xff08;版本 ≥ 20.10&#xff09;Docker Compose&#xff08;版本 ≥ v2.0&#xff09; 域…

以pytest_addoption 为例,讲解pytest框架中钩子函数的应用

钩子函数&#xff08;Hook Function&#xff09;的概念 钩子函数&#xff08;Hook Function&#xff09;是软件框架中预定义的回调接口&#xff0c;允许开发者在程序执行的特定阶段插入自定义逻辑&#xff0c;以扩展或修改框架的默认行为。在 pytest 中&#xff0c;钩子函数覆…

合并两个有序链表 - 简单

************* C topic: 21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; ************* Give the topic an inspection. Hi, guys, how is your holiday break? I went to 黄山 in the past few days. The mount Huang is really beautiful. 天都峰 is real…

13.Spring boot中使用Actuator 监控

13.Spring boot中使用Actuator 监控 Spring Boot Actuator 是 Spring Boot 提供的一个强大的监控和管理工具&#xff0c;它通过暴露各种端点&#xff08;Endpoints&#xff09;来提供应用程序的运行时信息。这些端点可以帮助开发者和管理员监控应用程序的健康状况、性能指标、…

Python+Scrapy跨境电商爬虫实战:从亚马逊/沃尔玛数据采集到反爬攻克(附Pangolin API高效方案)

从零实战到反爬攻克&#xff0c;揭秘跨境数据抓取全流程与Pangolin Scrape API终极方案 在当今数据驱动的跨境电商时代&#xff0c;谁掌握了优质的市场数据&#xff0c;谁就掌握了成功的关键。随着全球电商市场规模持续扩大&#xff08;据Statista最新报告显示&#xff0c;2025…

0基础学习鸿蒙开发-HarmonyOS4

一、初识 1. 开发工具 官网 开发-HarmonyOS NEXT鸿蒙应用开发平台-华为开发者联盟 2. ArkTS 二、TypeScript 基本语法 1.变量声明 2. 条件控制 注意 在TypeScrips中 空字符串数字0、null、undefined 都坡认为是false 其它值则为true if (num) {// num 非空执行 } 3. 循环迭…

深度学习中常用的符号表达式

在论文写作过程中&#xff0c;常常涉及到一些关键的符号的表达&#xff0c;为了更加规范常用的一些符号表达&#xff0c;现将其总结如下&#xff08;该文件会持续性更新&#xff09;&#xff1a; 数字 x x x : 标量 x \mathbf{x} x : 向量 X \mathbf{X} X : 矩阵 X \mathsf{X}…

react naive 网络框架源码解析

本文取 react native 两个区别很大的版本做分析&#xff08;0.76.5、0.53.3&#xff09; 一、0.76.5 版fetch 全流程排查 1、JS 端的实现 随手写一个fetch&#xff0c;点开。 我们这里常用的还是手机端&#xff0c;因此选择 react-native&#xff0c;react-native-windows …

OpenCV 图形API(81)图像与通道拼接函数-----透视变换函数warpPerspective()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 对图像应用透视变换。 函数 warpPerspective 使用指定的矩阵对源图像进行变换&#xff1a; dst ( x , y ) src ( M 11 x M 12 y M 13 M 31 x…

深度学习在油气地震资料反卷积中的应用

深度学习在油气地震资料反卷积中的应用 基本原理 在油气地震勘探中&#xff0c;反卷积(Deconvolution)是一种重要的信号处理技术&#xff0c;用于提高地震资料的分辨率。传统方法(如维纳滤波、预测反卷积等)存在对噪声敏感、假设条件严格等局限。深度学习方法通过数据驱动的方…

Java开发者面试实录:微服务架构与Spring Cloud的应用

面试场景 面试官: 请介绍一下你的基本情况。 程序员: 大家好&#xff0c;我叫张小明&#xff0c;今年27岁&#xff0c;硕士学历&#xff0c;拥有5年的Java后端开发经验。主要负责基于Spring Boot开发企业级应用&#xff0c;以及微服务架构的设计和实现。 面试官: 好的&#…

​Spring + Shiro 整合的核心要点及详细实现说明

在 Spring 项目中集成 Apache Shiro 可以实现轻量级的安全控制&#xff08;认证、授权、会话管理等&#xff09;。以下是 ​Spring Shiro 整合的核心要点及详细实现说明&#xff1a; 一、Spring 与 Shiro 整合的核心组件 ​组件​​作用​ShiroFilterFactoryBean创建 Shiro 过…

网络编程核心技术解析:从Socket基础到实战开发

网络编程核心技术解析&#xff1a;从Socket基础到实战开发 一、Socket编程核心基础 1. 主机字节序与网络字节序&#xff1a;数据传输的统一语言 在计算机系统中&#xff0c;不同架构对多字节数据的存储顺序存在差异&#xff0c;而网络通信需要统一的字节序标准&#xff0c;这…

SQLark可以支持PostgreSQL了,有哪些新功能?

SQLark&#xff08;百灵连接&#xff09;是一款国产的数据库开发和管理工具&#xff0c;用于快速查询、创建和管理不同类型的数据库系统&#xff0c;支持达梦、Oracle 和 MySQL 数据库。 最新发布的 SQLark V3.4 版本新增了对 PostgreSQL 数据库的支持。我试用了一下&#xff…