现代C++(Modern C++)基本用法实践:三、移动语义
概述
移动
移动(move)语义C++引入了一种新的内存优化,以避免不必要的拷贝。在构造或者赋值的时候,如果实参是右值(或者左值由std::move转换成右值),便会匹配移动语义的函数调用如下述举例的Str(Str&& obj)。
移动语义的本质是将资源(内存/句柄)转移给另一个对象,被转移资源的对象不应再被使用。(这个概念有点像仙侠小说中的夺舍,夺舍成功的人获取被夺舍的人的身体(资源)),如下面伪代码:
class Obj
{
data
Obj(){
data = malloc(100)
}
// 移动 (夺舍)
Obj(Obj&& other){
data = other.data
other.data = nullptr
}
}
右值
右值直观理解是等号右边的值(大概如此,并不准确),右值的概念指代的东西比较多,大概是指不可寻址的值(也有例外)。我觉得这个不必太过纠结,记住几个常见的即可:
- 临时对象:如函数返回的临时对象(下面有举例)
- 字面量
- 显式std::move()转换的值
- 没有捕获参数的lambda
C++ 值类别表
在 C++11之后,C++根据
- 被标识:可通过不同标识符指代同一实体。(对象/内存)
- 可移动:可作为移动语义函数的参数,例如移动构造,移动赋值。
将值分为以下类别:
- 泛左值:被标识
- 左值:被标识且不可移动
- 将亡值:被标识可移动
- 右值:可移动
- 将亡值:被标识可移动
- 纯右值:不被标识且可移动
用法举例
参考测试项目代码ModernCppTest/modrenc_rvalueref_stdmove.cpp
主要内容:
- 移动语义下的构造和赋值
- 移动还是拷贝的重载匹配
- C++ 优化临时对象(连加产生的中间临时对象)尝试调用移动语义
#include "ModernCppTestHeader.h"
#include <string>
using std::string;
namespace n_rvalueref {
class Str {
public:
Str() {
LOG("无参构造");
this->str = new string();
}
Str(const string& str) {
LOG("有参构造 str = " << str);
this->str = new string(str);
}
Str(const Str& obj) {
LOG("拷贝构造 obj.str = " << *obj.str);
this->str = new string(*obj.str);
}
Str(Str&& obj) noexcept {
LOG("移动构造 obj.str = " << *obj.str);
this->str = std::move(obj.str);
// 被移动的对象不应该再被使用了
obj.str = nullptr;
}
Str& operator=(Str&& v) noexcept {
LOG("移动语义 operator = ");
if (this != &v) {
this->str = std::move(v.str);
}
return *this;
}
Str operator+(const Str& v)
{
string s = *this->str + *v.str;
return Str(s);
}
void Log()
{
LOG(str);
}
string* str;
};
}
using n_rvalueref::Str;
// 右值引用&移动语义
void rvalueref_stdmove_test()
{
LOG_FUNC();
LOG_TAG("拷贝构造");
{
Str t1("A");
Str t2 = t1;
LOG_VAR(*t2.str);
}
LOG_TAG("移动构造, 注意被移动的对象t1不应再被使用");
{
// t1是左值,使用std::move强制转换成右值
Str t1("A");
Str t2 = std::move(t1);
LOG_VAR(*t2.str);
}
LOG_TAG("移动语义的运算符重载,注意运算符重载发生赋值运算(这个例子),而不是构造运算(上个例子)");
{
Str t1("A");
Str t2;
t2 = std::move(t1);
}
LOG_TAG("除了上述显示使用std::move转换,常见的容易忽视的发生移动构造场合列举");
{
LOG("---1 连续加法产生的临时对象,c++会尝试使用移动语义进行优化");
Str t1("A");
Str t2("B");
Str t3("C");
Str t4;
t4 = t1 + t2 + t3;
LOG("---2 函数返回的临时对象,c++会尝试使用移动语义进行优化");
auto f = []() {
auto s = Str("Hi");
return s;
};
Str t5 = f();
/*
- 在容器中插入或删除元素:比如 std::vector::push_back,如果传递给它的是右值,它就会使用移动语义。
- 在标准库算法中:许多标准库算法,比如 std::sort,std::partition 等,在进行元素交换时会使用移动语义。
- 在 std::swap 中:std::swap 会使用移动语义来交换两个对象。
*/
}
}
现代C++(Modern C++)基本用法实践:三、移动语义的更多相关文章
- nodejs 实践:express 最佳实践(三) express 解析
nodejs 实践:express 最佳实践(三) express 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固, ...
- Linux中sed的用法实践
Linux中sed的用法实践 参考资料:https://www.cnblogs.com/emanlee/archive/2013/09/07/3307642.html http://www.fn139 ...
- WebSocket原理与实践(三)--解析数据帧
WebSocket原理与实践(三)--解析数据帧 1-1 理解数据帧的含义: 在WebSocket协议中,数据是通过帧序列来传输的.为了数据安全原因,客户端必须掩码(mask)它发送到服务器的所有 ...
- Linux及安全实践三——程序破解
Linux及安全实践三--程序破解 一.基本知识 常用指令机器码 指令 作用 机器码 nop 无作用(no operation) 90 call 调用子程序,子程序以ret结尾 e8 ret 返回程序 ...
- jquery jtemplates.js模板渲染引擎的详细用法第三篇
jquery jtemplates.js模板渲染引擎的详细用法第三篇 <span style="font-family:Microsoft YaHei;font-size:14px;& ...
- Linux find命令的用法实践
一.find命令简介 Linux下find命令在目录结构中搜索文件,并执行指定的操作.Linux下find命令提供了相当多的查找条件,功能很强大.由于find具有强大的功能,所以它的选项也很多,其中大 ...
- AI (Adobe Illustrator)详细用法(三)
本文主要是介绍和色彩相关的用法. 一.路径外观设置 1.设置描边粗细 手动输入20px 下拉选择 鼠标选中数值,按向上或向下的箭头调整 在右边的描边菜单中修改 Note:按住shift键,然后上下箭头 ...
- Android最佳性能实践(三)——高性能编码优化
在前两篇文章当中,我们主要学习了Android内存方面的相关知识,包括如何合理地使用内存,以及当发生内存泄露时如何定位出问题的原因.那么关于内存的知识就讨论到这里,今天开始我们将学习一些性能编码优化的 ...
- 并发编程实践三:Condition
Condition实例始终被绑定到一个锁(Lock)上.Lock替代了Java的synchronized方法,而Condition则替代了Object的监视器方法,包含wait.notify和noti ...
- ansible playbook实践(三)-yaml文件写法
playbook基于YAML语法来编写,基本语法规则如下: 1.大小写敏感 2.使用缩进表示层级关系 3.缩进时不允许使用Tab键,只允许使用空格 4.缩进的空格数目不重要,只要相同层级的元素左侧对齐 ...
随机推荐
- 【Vue2.x源码系列07】监听器watch原理
上一章 Vue2计算属性原理,我们介绍了计算属性是如何实现的?计算属性缓存原理?以及洋葱模型是如何应用的? 本章目标 监听器是如何实现的? 监听器选项 - immediate.deep 内部实现 初始 ...
- 快速上手Linux核心命令(四):文件内容相关命令
@ 目录 前言 cat 合并文件或查看文件内容 more 分页显示文件内容 less 分页显示文件内容 head 显示文件内容头部 tail 显示文件内容尾部 tailf 跟踪日志文件 diff 比较 ...
- SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘
SpringBoot 集成 SpringSecurity + MySQL + JWT 无太多理论,直接盘 一般用于Web管理系统 可以先看 SpringBoot SpringSecurity 基于内存 ...
- workerman、websocket简单聊天功能从0到1实现
一.workerman安装,测试环境为linux 1.在网站根目录,用命令行安装,要是出现root权限提示,输入y回车就行,或者新建一个用户再进入安装 Linux系统可以使用以下脚本测试本机PHP环境 ...
- 获取android app 的Activity 和 Package
开头 appium 配置, sdk 配置,jdk配置,adb配置,python配置是我们app 自动化测试必不可少的配置,当然这种配置网上有很多,我们在这里就不展开说了. 直接就开始自动化脚本的dem ...
- 2022-09-27:给定一个棵树, 树上每个节点都有自己的值,记录在数组nums里, 比如nums[4] = 10,表示4号点的值是10, 给定树上的每一条边,记录在二维数组edges里, 比如ed
2022-09-27:给定一个棵树, 树上每个节点都有自己的值,记录在数组nums里, 比如nums[4] = 10,表示4号点的值是10, 给定树上的每一条边,记录在二维数组edges里, 比如ed ...
- 2021-08-12:约瑟夫环问题。给定一个链表头节点head,和一个正数m,从头开始,每次数到m就杀死当前节点 ,然后被杀节点的下一个节点从1开始重新数, 周而复始直到只剩一个节点,返回最后的节点。
2021-08-12:约瑟夫环问题.给定一个链表头节点head,和一个正数m,从头开始,每次数到m就杀死当前节点 ,然后被杀节点的下一个节点从1开始重新数, 周而复始直到只剩一个节点,返回最后的节点. ...
- Java基础--数据结构
数据结构 Java工具包提供了强大的数据结构.在Java中的数据结构主要包括以下几种接口和类: 枚举(Enumeration).位集合(BitSet).向量(Vector).栈(Stack).字典(D ...
- 2023-05-20:go语言的slice和rust语言的Vec的扩容流程是什么?
2023-05-20:go语言的slice和rust语言的Vec的扩容流程是什么? 答案2023-05-20: go语言的slice扩容流程 go版本是1.20.4. 扩容流程见源码见runtime/ ...
- SpringBoot 使用 Sa-Token 完成路由拦截鉴权
一.需求分析 在前文,我们详细的讲述了在 Sa-Token 如何使用注解进行权限认证,注解鉴权虽然方便,却并不适合所有鉴权场景. 假设有如下需求:项目中所有接口均需要登录认证校验,只有 "登 ...