当前位置: 首页 > news >正文

【C++】移动语义与move()实用性教学

一、本质定义

移动语义的本质是资源的 “窃取”或 “接管”。

         移动语义是C++11引入的资源管理机制,通过转移而非拷贝对象资源的所有权来提升性能。其核心在于右值引用(&&)和移动构造/赋值函数的组合实现。

        移动语义是一种特殊的行为或者叫操作,std::move()函数是其中一个体现了移动语义的操作,并不是说移动语义一定是使用了std::move()函数。常见的实现了移动语义的操作还有"移动构造","移动赋值运算符",所有实现了移动语义的操作,肯定是将资源(数据或者文件)的管理权转移给其他变量管理,都不涉及释放资源和重新构造操作。

二、解决什么问题

(拷贝与移动两种方案的概念和代码对比分析)

传统拷贝方案:在传统的 C++ 中,当进行对象复制时,比如通过拷贝构造函数或拷贝赋值运算符,会创建目标对象的一份完整的副本,这涉及对资源(如动态内存、文件句柄等)的复制。       

移动语义方案:移动语义允许我们将临时对象(通常是右值)所拥有的资源直接转移给目标对象,而不是进行复制。例如,在处理一个包含动态分配内存的类时,移动构造函数可以直接将临时对象的指针转移到新对象,而不用再分配内存并复制内容,这样大大减少了资源开销

以下是两种方案处理字符串赋值的操作对比,其中传统拷贝方案实现的效果是"给别人一份一样的";移动语义方案实现的效果是"拿走别人的那一份"。

#include <cstring>
#include <utility>
#include <iostream>class String {
private:char* data;     // 只是声明,还没有分配内存空间public:// 基础构造函数explicit String(const char* str = "") {data = new char[strlen(str) + 1];strcpy_s(data, strlen(str) + 1, str);}// 传统拷贝构造函数String(const String& src) {data = new char[strlen(src.data) + 1];strcpy_s(data, strlen(src.data) + 1, src.data);std::cout << "Copied\n";}// 移动构造函数String(String&& src) noexcept {data = src.data;src.data = nullptr;std::cout << "Moved\n";}~String() { delete[] data; }
};void process(String s) { /* 使用副本 */ }int main() {String s1("Hello");// 1.传统拷贝方案String s2 = s1;          // 输出"Copied"process(s1);             // 输出"Copied"// 2.移动语义方案String s3 = std::move(s1);  // 输出"Moved"process(std::move(s3));     // 输出"Moved"return 0;
}

三、什么功能

        从代码实现的角度来看,移动语义只有一个操作,那就是将"资源管理权转移给别人"。移动语义的功能,作用,意义都是从此衍生出来的,包括:有效避免大型资源文件的深拷贝,避免资源的重复分配和释放;转移资源管理权,根据需求更换管理者。

四、怎么使用

        (交代代码结构)

        观察以下代码可以感受移动语义代码的触发时机, 代码编写逻辑,实际效果。其实下方代码结构很简单,主要是一个Resource类中,成员变量有两个,成员函数包含2个用于创建新对象的构造函数,2个用于重新赋值的'='重载运算符,移动语义就是这两个重载运算符中体现的。


#include <iostream>
#include <utility>class Resource {
private:int* data;std::string name;
public:// 1. 拷贝构造函数Resource(const Resource& other) : data(new int(*other.data)), name(other.name) {print("拷贝构造");}// 2. 移动构造函数Resource(Resource&& other) noexcept : data(other.data), name(std::move(other.name)) {other.data = nullptr;print("移动构造");}// 3. 拷贝赋值运算符Resource& operator=(const Resource& other) {if(this != &other) {delete data;data = new int(*other.data);name = other.name;}print("拷贝赋值");return *this;}// 4. 移动赋值运算符Resource& operator=(Resource&& other) noexcept {if(this != &other) {delete data;data = other.data;name = std::move(other.name);other.data = nullptr;}print("移动赋值");return *this;}// 辅助函数void print(const std::string& msg) const {std::cout << msg << " => data:" << (data ? *data : 0) << " name:" << name << std::endl;}// 基础构造/析构Resource(int val, std::string n) : data(new int(val)), name(n) {}~Resource() { delete data; }
};int main() {Resource r1(1, "原始资源");Resource r2 = r1;          // 拷贝构造Resource r3(std::move(r1));// 移动构造(触发移动语义)Resource r4(2, "临时");r4 = r2;                   // 拷贝赋值r4 = std::move(r3);        // 移动赋值(触发移动语义)return 0;
}

五、move()原理

(代码+解释)

std::move()是一个库函数,主要用于转移资源管理权,参数可以接收左值引用和右值引用类型,返回值一定是该参数变量的右值引用类型,原理是根据推导规则区分出参数变量的实际引用类型,一次决定返回值类型,使用类型强制转换操作符,将参数变量强制转换成右值引用类型,作为返回值。这样做的意义是将一个变量转换成右值引用。

template<typename T>
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept {return static_cast<std::remove_reference_t<T>&&>(t);
}

①constexpr:  告诉编译器,被我修饰的函数或者表达式,可以在"编译时"求值。 

②std::remove_reference_t<T> : 去除引用类型,将左值引用和右值引用都还原成基础类型。

③std::remove_reference_t<T> &&:组合效果,相当于(基础类型+&&)即变成右值引用类型。

④模板类函数的参数列表中的T&&:这是通用引用(也叫转发引用),可以同时接受左值引用和右值引用类型的变量,都可以作为参数,而且由于"类型推导规则"的存在,是能够区分出到底接受的是什么类型的引用聚。

⑤noexcept:告诉编译器,这个函数绝对不会抛出异常,不需要对这部分内容检查。

⑥static_cast<目标类型>(原始类型的变量):强制将原始类型的变量,转换为目标类型的变量,在此处的意义是,强行将类型为T的变量t,强制转换为,T&&右值引用类型的变量t。

作者结束语

        (总结是每篇文章独立的,结语是我固定的)

        这篇文章的结构安排应该是很清晰了,标题顺序,内容组织,代码解释都还不错,技术类文章的任务就是要教大家认识它,理解它,使用它。文章的内容我检查过很多次了,每篇文章都是一边查资料,一边理解整理,输出成文章,分享我的思考。点个免费的赞吧~

后续更多同样形式的分享,希望能通过我的文章给你更多思维的启发,更多思考问题的角度。

相关文章:

  • Docker网关冲突导致容器启动网络异常解决方案
  • 蓝桥杯3503 更小的数
  • Podman(Pod Manager)简介
  • Zabbix开源监控的全面详解!
  • Docker面试题(1)
  • ADB常用语句
  • Python 包管理工具uv常用场景使用指南
  • OpenCv高阶(十四)——LBPH人脸识别
  • 线性表数据结构-队列
  • 电感在断开的时候会按原来的电流方向流动这是什么定理?
  • 【每周一个MCP】:将pytdx封装成MCP
  • STL中的Vector(顺序表)
  • Flannel后端为UDP模式下,分析数据包的发送方式(一)
  • 【npm】npm命令大全
  • Vue3中插槽, pinia的安装和使用(超详细教程)
  • Xilinx 7Series\UltraScale 在线升级FLASH STARTUPE2和STARTUPE3使用
  • 关于收集 Android Telephony 网络信息的设计思考
  • 进程调度算法深度剖析:FCFS、SJF、RR、优先级及多级反馈队列全解
  • 【软件设计师】计算机网络考点整理
  • adb抓包
  • 网站建设协议书 保密条款/互联网平台
  • 门户网站建设课程设计/千锋教育和达内哪个好
  • 信誉好的龙岗网站建设/seo服务深圳
  • 岳阳网站制作/网络营销的现状和发展趋势
  • 维护网站费用怎么做会计凭证/seo外包优化
  • 成都网站制作创新互联/网站媒体推广方案