【C++ | 移动构造函数】一文了解C++11的 移动构造函数

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:2024-06-12 23:36:22

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
    • ✨1.1 移动语义
    • ✨1.2 右值引用
  • 🎄二、移动构造函数(move constructor)
  • 🎄三、使用移动构造函数
  • 🎄四、总结



在这里插入图片描述

🎄一、概述

移动构造函数、移动赋值函数都是C++11增加的,了解这两个函数之前需要先弄清楚 移动语义右值引用

✨1.1 移动语义

移动语义是C++11才实现的,它的意义是对于一些即将被销毁的对象,可以不复制它,而是窃取它的资源,以此来减少一次复制,达到大幅度提升性能的效果。

在C++11标准之前,没有直接的方法移动对象,即使某些情况不需要拷贝对象,也不得不拷贝。例如,函数返回非引用的返回值时,会先构造一个临时对象作为返回值,函数调用时将该临时对象赋值给接收对象,调用结束后会销毁该临时对象。这里的临时对象是肯定会被销毁的,如果能够将临时对象的资源直接移交给接收对象,那么就可以减少一次拷贝。旧标准则只能拷贝,然后再销毁临时对象的资源。
在这里插入图片描述
C++11支持将即将被销毁的对象移动到接收对象,而不是复制。如下图,临时对象的资源被移动到接收对象,减少一次复制。
在这里插入图片描述
下面代码演示了复制的过程,等学习了下面的移动构造函数就可以编写移动的代码了。

// g++ 14_Copy_Date.cpp -std=c++11
#include <iostream>
#include <stdio.h>
#include <string.h>using namespace std;#define MAX_NEW_MEM		(64*1000*1000)class CDate
{
public:CDate(int year, int mon, int day);	// 构造函数声明CDate(const CDate& date);			// 拷贝构造函数声明~CDate();							// 析构函数声明CDate operator+(int day);			// 加号运算符声明void show(){cout << "Date: " << m_year << "." << m_mon << "." << m_day << ", this=" << this << endl;//cout << "Date: " << str << endl;}private:int m_year;int m_mon;int m_day;char *str;
};// 构造函数定义
CDate::CDate(int year, int mon, int day)
{m_year = year;m_mon = mon;m_day = day;str = new char[MAX_NEW_MEM];sprintf(str, "%4d.%02d.%02d", year,mon,day);cout << "Calling Constructor" << ", this=" << this <<endl;
}// 拷贝构造函数定义
CDate::CDate(const CDate& date)
{m_year = date.m_year;m_mon = date.m_mon;m_day = date.m_day;str = new char[MAX_NEW_MEM];memcpy(str, date.str, MAX_NEW_MEM);cout << "Calling Copy Constructor" << ", this=" << this << ", Copy Data" <<endl;
}// 析构函数定义
CDate::~CDate()
{cout << "Calling Destructor" << ", this=" << this <<endl;delete [] str;
}CDate CDate::operator+(int day)
{CDate temp = *this;temp.m_day += day;cout << "Calling operator+" << ", this=" << &temp << endl;return temp;
}int main()
{CDate date(2024,06,07);cout << endl;CDate date1 = std::move(date+1);// std::move 强制将 date+1 的求值结果转为右值date1.show();cout << endl;return 0;
}

运行结果如下:
1、std::move 强制将 date+1 的求值结果转为右值,避免编译器优化;
2、temp 对象赋值给date1之后,就销毁了,如果可以直接将temp对象的资源给到date1,就可以减少一次复制。
在这里插入图片描述


✨1.2 右值引用

前面介绍了C++11的移动语义,那这个移动语义是怎样实现的呢?

C++11标准引入的一种新的引用类型——右值引用,来实现移动操作。右值引用只能关联到右值,而右值要么是字面值,要么是求值过程中产生的临时对象。右值引用的定义使用了&&,可以参考下面代码:

int &&rri = 13; // 定义了右值引用 rri

上个小节的临时对象只会在程序中很短暂的停留,使用完之后会被立即销毁。C++11标准通过 右值引用 来完全地接管这些临时对象,然后将临时对象的资源 移动 到接收对象,并且要保证这个临时对象可以被正常销毁。这就是C++11标准实现移动语义的原理。

关于左值、右值、右值引用还有疑惑的,可以参考这篇文章:【C++ | 左值、右值】一文了解C++的左值、右值、左值引用(&)、右值引用(&&)


在这里插入图片描述

🎄二、移动构造函数(move constructor)

移动构造函数(move constructor),是C++11标准增加的在创建对象时移动旧对象资源的构造函数。

类的移动构造函数原型通常是这样的:类名(类名 &&);。以CDate类为例,其移动构造函数声明如下:

CDate(CDate &&date);

移动构造函数的几个特点:
1、函数名和类名相同,没有返回值,因为它也是构造函数的一种;
2、第一个参数必须是一个自身类类型的右值引用(&&),且其他参数都有默认值。
3、第一个参数不能声明为 const 右值引用的原因是该引用在函数内会被修改(移动资源)。
4、移动构造函数执行后,需要保证右值引用的对象能够被正常销毁。

下面看看怎样声明定义自己的移动构造函数:

CDate(CDate &&date) noexcept;	// 声明
CDate::CDate(CDate&& date)		// 实现
{m_year = date.m_year;m_mon = date.m_mon;m_day = date.m_day;str = date.str;date.str = NULL;cout << "Calling Move Constructor" << ", this=" << this <<endl;
}

移动构造函数解析:

  • 1、处理普通数据成员,直接使用旧对象的进行赋值;
  • 2、处理指向堆内存的指针,直接堆内存地址给新对象的指针,旧对象指针指向NULL。
    语句str = date.str;将旧对象的str直接给到正在创建的对象,而不重新new、复制。
    语句date.str = NULL;将旧对象的str赋值为NULL,保证其可以被正常销毁,delete NULL;delete[] NULL不会造成任何问题。
  • 3、由于移动操作“ 窃取” 资源, 它通常不分配任何资源。 因此, 移动操作通常不会抛出任何异常。不抛出异常的函数应该使用 noexcept 通知标准库,避免编译器为了处理异常而作一些额外的工作。

在这里插入图片描述

🎄三、使用移动构造函数

1、使用移动构造函数就需要弄清楚移动构造函数是什么时候会被调用的?
如果使用一个右值(即将被销毁的对象)去初始化同类型的对象,就会调用该类的移动构造函数。

2、怎样的类需要定义移动构造函数?
如果该类的成员存在指针指向new分配的堆内存,则可以跟进需要定义移动构造函数。

3、什么时候编译器会提供默认移动构造函数?
如果该类没有定义拷贝构造函数,且没有定义移动构造函数,则编译器会提供一个 默认的移动构造函数
如果该类定义了拷贝构造函数,则编译器不会提供 默认的移动构造函数,需要用到移动构造的地方都会调用拷贝构造函数。
如果该类定义了移动构造函数,则编译器不会提供 默认的拷贝构造函数,需要用到拷贝构造的时候,编译器会报错。

4、默认的移动构造函数做了什么工作?
默认的移动构造函数 所做的工作与默认拷贝构造函数的工作一样,执行逐成员初始化并复制内置类型。如果成员是类对象,将使用相应类的构造函数和赋值运算符,就像参数为右值一样。

下面是第一小节的代码,加了移动构造函数后的例子:

// g++ 14_Move_Constructor_Date.cpp -std=c++11
#include <iostream>
#include <stdio.h>
#include <string.h>using namespace std;#define MAX_NEW_MEM		(64*1000*1000)class CDate
{
public:CDate(int year, int mon, int day);	// 构造函数声明CDate(const CDate& date);			// 拷贝构造函数声明CDate(CDate&& date) noexcept;		// 移动构造函数声明~CDate();							// 析构函数声明CDate operator+(int day);			// 加号运算符声明void show(){cout << "Date: " << m_year << "." << m_mon << "." << m_day << ", this=" << this << endl;//cout << "Date: " << str << endl;}private:int m_year;int m_mon;int m_day;char *str;
};// 构造函数定义
CDate::CDate(int year, int mon, int day)
{m_year = year;m_mon = mon;m_day = day;str = new char[MAX_NEW_MEM];sprintf(str, "%4d.%02d.%02d", year,mon,day);cout << "Calling Constructor" << ", this=" << this <<endl;
}// 拷贝构造函数定义
CDate::CDate(const CDate& date)
{m_year = date.m_year;m_mon = date.m_mon;m_day = date.m_day;str = new char[MAX_NEW_MEM];memcpy(str, date.str, MAX_NEW_MEM);cout << "Calling Copy Constructor" << ", this=" << this << ", Copy Data" <<endl;
}CDate::CDate(CDate&& date) noexcept
{m_year = date.m_year;m_mon = date.m_mon;m_day = date.m_day;str = date.str;date.str = NULL;cout << "Calling Move Constructor" << ", this=" << this <<endl;
}// 析构函数定义
CDate::~CDate()
{cout << "Calling Destructor" << ", this=" << this <<endl;delete [] str;
}CDate CDate::operator+(int day)
{CDate temp = *this;temp.m_day += day;cout << "Calling operator+" << ", this=" << &temp << endl;return temp;
}int main()
{CDate date(2024,06,07);cout << endl;CDate date1 = std::move(date+1);// std::move 强制将 date+1 的求值结果转为右值date1.show();cout << endl;return 0;
}

运行结果:可以看到相比于第一小节的代码,这里调用了移动构造函数,减少了一次拷贝。
在这里插入图片描述


在这里插入图片描述

🎄四、总结

👉本文介绍C++11的移动构造函数,讲解了为什么需要移动构造函数,怎样声明、定义移动构造函数,怎样使用移动构造函数。

移动构造函数的目的就是为了减少一次拷贝,提升性能。C++11主要通过右值引用来实现移动语义,在使用右值(即将销毁的对象)去创建对象时,调用移动构造函数而不调用拷贝构造函数,以此减少一次拷贝。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

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

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

相关文章

Linux基础IO【II】真的很详细

目录 一.文件描述符 1.重新理解文件 1.推论 2.证明 2.理解文件描述符 1.文件描述符的分配规则 3.如何理解文件操作的本质&#xff1f; 4.输入重定向和输出重定向 1.原理 2.代码实现重定向 3.dup函数 ​编辑 4.命令行中实现重定向 二.关于缓冲区 1.现象 …

【学习笔记】Linux

Linux 1、 介绍 1.1、概述 1.2、特点 1.3、Linux的发行版2、 基础篇 —— 文件系统 2.1、文件系统 2.2、目录结构3、 基础篇 —— VI/VIM 编辑器 3.1、概述 3.2、编辑器模式及常用命令4、 基础篇 —— 网络配置 4.1、VMware NetWork …

电路笔记 : 嘉立创EDA 导入、查找、设计管理器(快速寻找网络标签)功能+DRC错误检查和处理

导入功能 查找功能 可查找多种类型&#xff0c;如原件名称、网络标签等 设计管理器 图层查看 DRC错误 规则设置 线距问题 大多数PCB制造商能够可靠地生产5 mil间距的走线和间隙。这是一个常见的标准&#xff0c;适合大多数消费级和工业级电子产品。在5 mil以上的间距&#xff…

Android APP memory统计方法

目录 进程的内存信息概述 关键的术语 测试步骤 测试步骤 数据处理 数据分析&#xff1a; 进程内存信息 Dumpsys meminfo -a PID Procrank Procmem PID 特殊内存信息 Mali ION(multi-media&#xff0c;gralloc) 进程地址空间信息 /proc/pid/smaps Showmap PID …

计算机网络 —— 运输层(TCP三次握手)

计算机网络 —— 运输层&#xff08;TCP三次握手&#xff09; 三次握手第一次握手第二次握手第三次握手两次握手行不行&#xff1f; 我们今天来学习TCP的三次握手&#xff1a; 三次握手 TCP三次握手是TCP协议中建立连接的过程&#xff0c;旨在确保双方准备好进行可靠的通信。…

【HarmonyOS】鸿蒙入门学习

一、开发前的准备 &#xff08;一&#xff09;HarmonyOS 开发套件介绍 &#xff08;二&#xff09;开发者主要使用的核心套件 主要为代码开发阶段会使用到的 DevEco Studio、ArkTS、ArkUI三个工具。 &#xff08;三&#xff09;熟悉鸿蒙官网 1、网址 https://developer.hua…

第P10周:Pytorch实现车牌识别

第P10周&#xff1a;Pytorch实现车牌识别 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 在之前的案例中&#xff0c;我们多是使用datasets.ImageFolder函数直接导入已经分类好的数据集形成Dataset&#xff…

创建节约机关怎样向媒体投稿报道宣传?

创建节约机关并向媒体投稿报道宣传是一项重要的工作&#xff0c;它不仅能够提升机关的形象&#xff0c;还能促进社会各界对节约型社会的认识和支持。 作为一名新晋信息宣传员,初入职场的我满腔热血,怀揣着用文字传递价值的理想,却在投稿的道路上屡遭波折。面对每月的宣传任务,我…

自动控制原理【期末复习】(二)

无人机上桨之后可以在调试架上先调试&#xff1a; 1.根轨迹的绘制 /// 前面针对的是时域分析&#xff0c;下面针对频域分析&#xff1a; 2.波特图 3.奈维斯特图绘制 1.奈氏稳定判据 2.对数稳定判据 3.相位裕度和幅值裕度

飞腾派初体验(2)

水个字数&#xff0c;混个推广分&#xff0c;另外几个点还是想吐槽一下 - 1&#xff0c;上篇文章居然没有给开发板一个硬照&#xff0c;补上 - 飞腾派 自拍 2. 现在做镜像用Win32DiskImager的多吗&#xff1f;我记得当年都是dd命令搞定&#xff0c;玩树莓派的应该记得这个命令…

linuxcentos将本地库JAR/arr批量导入到Nexus3.x

背景 我们现在要搞一个私服maven来管理对应的依赖包&#xff0c;需要上传包。用nexus只能单个文件搞&#xff0c;批量导入不行&#xff0c;而且还要单独配置groupID什么的。不多BB,上教程 建脚本 vi mavenimport.sh内容是这个 #!/bin/bash # copy and run this script to t…

6.13--CSS

行内样式 <!DOCTYPE html> <html lang"en"> <head> <title>This is title</title> </head> <body> <p style"font-size: 16px; color: red;">大家好</p> </body> </html> 内部样式表…

piRNA-疾病研究最新综述 – 数据库、软件工具、webserver和计算模型

piRNA被世界顶尖科学期刊《Science》评为2006年年度十大科学突破之一。之后有关piRNA的研究也成为CNS文章的常客。 2022年6月7日发表在Briefings in Bioinformatics上的这篇综述为我们整理了39个piRNA特异性和非特异性数据库及生物信息学资源&#xff1b;并根据piRNA的功能&…

UniApp或微信小程序中scroll-view组件使用show-scrollbar在真机Android或IOS中隐藏不了滚动条的解决办法

show-scrollbar 属性 不论是使用 变量 还是直接使用 布尔值或者直接使用 css 都是在 ios、Android 上是都没有效果。。 真机中还是出现滚动条 解决办法 添加下面CSS ::-webkit-scrollbar {display: none;width: 0 !important;height: 0 !important;-webkit-appearance: no…

贪吃蛇双人模式设计(2)

敲上瘾-CSDN博客控制台程序设置_c语言控制程序窗口大小-CSDN博客贪吃蛇小游戏_贪吃蛇小游戏csdn-CSDN博客 一、功能实现&#xff1a; 玩家1使用↓ → ← ↑按键来操作蛇的方向&#xff0c;使用右Shift键加速&#xff0c;右Ctrl键减速玩家2使用W A S D按键来操作蛇的方向&am…

网络编程(三)UDP TFTP协议

文章目录 一、 UDP&#xff08;一&#xff09;概述&#xff08;二&#xff09;流程 二、收发函数&#xff08;一&#xff09;recvfrom&#xff08;二&#xff09;sendto 三、实现一个简单的udp服务端和客户端四、实现tftp客户端协议 一、 UDP &#xff08;一&#xff09;概述 …

shell条件测试和if语句

“C:\Users\Jessica\Desktop\6.12.assets\image-20240612094159578.png”# shell条件测试和if语句 一、条件测试 1、$? 返回码 判断命令或者脚本是否执行成功&#xff08;最近的一条&#xff09; 0 true 为真就是成功 成立 非0 false 失败或者异常 2、test命令 可以进行…

Docker 镜像源更换

实现 替换docker 镜像源 前提要求 安装 docker docker-compose 参考创建一键更换docker国内镜像源 Docker 镜像代理DaoCloud 镜像站百度云 https://mirror.baidubce.com南京大学镜像站

【Qt 学习笔记】Qt窗口 | 对话框 | 创建自定义对话框

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt窗口 | 对话框 | 创建自定义对话框 文章编号&#xff1a;Qt 学习笔记…