【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) 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;
}

移动构造函数解析:

  • 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主要通过右值引用来实现移动语义,在使用右值(即将销毁的对象)去创建对象时,调用移动构造函数而不调用拷贝构造函数,以此减少一次拷贝。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

「Python-docx 专栏」docx 获取页面大小、设置页面大小(纸张大小)

本文目录 前言一、docx纸张大小介绍1、document.xml① 关于 document.xml 的一些知识点② 纸张大小在哪里③ 纸张大小都有啥④ EMU对应的尺寸列表二、获取docx纸张大小1、完整代码2、运行效果图三、python为docx设置纸张大小1、完整代码2、效果图前言 今天的这边文章,我们来说…

Java多线程基础知识-2

线程的3个方法&#xff1a; Thread.sleep()&#xff1a;当前线程睡眠多少毫秒&#xff0c;让给其他线程去执行。 Thread.yield()&#xff1a;当前线程退出一下&#xff0c;进入到等待队列&#xff0c;让其他线程执行&#xff0c;即让出线程一下。 Thread.join()&#xff1a;…

LabVIEW_TDMS

1.TDMS设置属性 想给这里写属性怎么整 使用TDMS设置属性函数时&#xff0c;对组名称与通道名称不设置&#xff0c;即可达到上图中的样式。 PS&#xff1a;属性名称如果设置一样则最终生效的值为最后写入的值。如将属性2修改为属性1&#xff0c;则最终只有1个属性1&#xff0c…

Pikachu靶场--文件上传

参考借鉴 Pikachu靶场之文件上传漏洞详解_皮卡丘文件上传漏洞-CSDN博客 文件上传漏洞&#xff1a;pikachu靶场中的文件上传漏洞通关_pikachu文件上传通关-CSDN博客 client check 在桌面新建一个文件夹&#xff0c;准备一个hello.php文件&#xff0c;文件写入如下代码 <?p…

Safari浏览器下载文件时,文件名会URL encoded

问题&#xff1a;相同链接下载文件&#xff0c;safari文件名编码异常 解决&#xff1a;response.setHeader("Content-Disposition", "attachment;filename*utf-8" URLEncoder.encode(filename, "UTF-8")); 问题描述 谷歌下载&#xff08;正常&a…

用AI帮助设计师提升工作效率?

在创意设计的世界里&#xff0c;效率往往意味着灵感与时间的完美结合。设计师们时常面临各种挑战&#xff0c;如何在保证作品质量的同时&#xff0c;又能提升工作效率&#xff1f;这不仅是设计师们思考的问题&#xff0c;也是AI技术正在积极解决的问题。那么&#xff0c;用AI帮…

2024年6月11日树莓派正式上市!

​Raspberry Pi 已在伦敦证券交易所上市&#xff0c;股票代码为 Raspberry Pi Holdings plc。 这是 Raspberry Pi 的分水岭&#xff0c;也是发展新阶段的开始&#xff1a;进入公开市场将使树莓派能够更快地制造出更多我们喜爱的产品。Raspberry Pi 基金会在首次公开募股中筹得的…

网络文化经营许可证:互联网时代的护航之舵

文网文由中国文化和旅游部&#xff08;原文化部&#xff09;颁发&#xff0c;旨在对网络文化活动进行规范管理&#xff0c;确保文化内容的健康积极。通过对文化市场的严格监管&#xff0c;文网文有效杜绝了低俗、违法、侵权等不良现象的发生。网络文化市场的良性发展&#xff0…

第四届人工智能、机器人和通信国际会议(ICAIRC 2024)

第四届人工智能、机器人和通信国际会议&#xff08;ICAIRC 2024&#xff09; 2024 4th International Conference on Artificial Intelligence, Robotics, and Communication 2024年12月27-29日 | 中国厦门 重要信息 会议官网&#xff1a;www.icairc.net 录用通知时间&…

Lucene重要特性及应用案例

文章目录 Elasticsearch Python 示例Lucene Java 示例Elasticsearch Python 示例进阶&#xff1a;多字段搜索与排序Lucene Java 示例进阶&#xff1a;使用TermQuery进行精确匹配 Lucene底层存储结构&#xff1a; Lucene作为高性能的全文搜索引擎库&#xff0c;其底层存储结构设…

【C++】类和对象(三)构造与析构

文章目录 一、类的6个默认成员函数二、 构造函数干嘛的&#xff1f;语法定义特性综上总结什么是默认构造函数&#xff1f; 三、析构函数干嘛的 &#xff1f;语法定义析构顺序 一、类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。空类中并不是真的什么…

不懂索引,简历上都不敢写自己熟悉SQL优化

大家好&#xff0c;我是考哥。 今天给大家带来MySQL索引相关核心知识。对MySQL索引的理解甚至比你掌握SQL优化还重要&#xff0c;索引是优化SQL的前提和基础&#xff0c;我们一步步来先打好地基。 当MySQL表数据量不大时&#xff0c;缺少索引对查询性能的影响不会太大&#x…

在react项目中让webpack使用mock数据

1. 创建react项目 使用 create-react-app 创建项目 npx create-react-app react-mock 执行 eject 命令 npm run eject 删除 package.json 文件中的 eslintConfig 选项 2. 安装依赖包 npm i path-to-regexp fast-glob chokidar axios 3. 创建中间件 在 config 文件夹中创…

Android【SDK】 SDK是如何开发的,怎么打包aar包

文章目录 一、Android SDK开发示例工程二、Android SDK的开发三、打包aar包四、Android SDK的使用 一、Android SDK开发示例工程 本教程工程Git链接&#xff1a;https://gitcode.com/xiaohuihui1400/AndroidSdkExample/overview 二、Android SDK的开发 新建项目&#xff0c;…

Linux驱动开发笔记(九)IIC子系统及其驱动

文章目录 前言一、IIC驱动框架二、总线驱动2.1 iic总线的运行机制2.2 重要数据结构2.2.1 i2c_driver结构体2.2.2 i2c总线结构体 2.3 匹配规则 三、设备树的修改四、设备驱动的编写4.1 相关API函数4.1.1 i2c_add_adapter( )4.1.2 i2c_register_driver( )4.1.3 i2c_transfer( )4.…

MDK-ARM 编译后 MAP 文件分析

本文配合 STM32 堆栈空间分布 食用更佳&#xff01; 一图胜千言。。。

【LLM之NL2SQL】DAIL-SQL论文阅读笔记

研究背景 该研究旨在提供一个全面、系统的评估框架&#xff0c;用于评估基于大型语言模型&#xff08;LLM&#xff09;的Text-to-SQL技术。特别强调了不同的提示工程策略的有效性和效率&#xff0c;以及开源LLM的可行性。研究的重点是评估在零样本和少样本场景下的不同问题表示…

ICC2如何写DCG需要的floorplan信息

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 DCG需要哪些floorplan信息呢? 1)fixed属性的port和mem / ip / io 2)boundary信息 3)电源域形状 前两条都可以写到def里,电源域需要用脚本处理,这里分享一下脚本。 set_fixed_objects [ge…

# 消息中间件 RocketMQ 高级功能和源码分析(七)

消息中间件 RocketMQ 高级功能和源码分析&#xff08;七&#xff09; 一、 消息中间件 RocketMQ 源码分析&#xff1a;消息存储核心类介绍 1、消息存储在 store 模块中。消息存储核心类 DefaultMessageStore.java 2、消息存储核心类介绍 private final MessageStoreConfig me…

react18 实现具名插槽

效果预览 技术要点 当父组件给子组件传递的 JSX 超过一个标签时&#xff0c;子组件接收到的 children 是一个数组&#xff0c;通过解析数组中各 JSX 的属性 slot &#xff0c;即可实现具名插槽的分发&#xff01; 代码实现 Father.jsx import Child from "./Child";…