第15课 右值引用(2)_std::move和移动语义
1. std::move
(1)std::move的原型
template<typename T>
typename remove_reference<T>::type&&
move(T&& param)
{
using ReturnType = remove_reference<T>::type&& return static_cast<ReturnType>(param);
}
(2)std::move的作用
①std::move函数的本质就是强制转换,它无条件地将参数转换为把一个右值引用,又由于函数返回的右值引用(匿名对象)是一个右值。因此,std::move所做的所有事情就是转换它的参数为一个右值。继而用于移动语义。
②该函数只是转换它的参数为右值,除此之外并没有真正的move任何东西。Std::move应用在对象上能告诉编译器,这个对象是有资格被move的。这也是为什么std::move有这样的名字:能让指定的对象更容易被move。
2. 移动语义
(1)深拷贝和移动的区别
①深拷贝:将SrcObj对象拷贝到DestObj对象,需要同时将Resourse资源也拷贝到DestObj对象去。这涉及到内存的拷贝。
②移动:是将资源的所有权从一个对象转移到另一个对象上。但只是转移,并没有内存的拷贝。可见Resource的所有权只是从SrcObj对象转移到DestObj对象,无须拷贝。
【编程实验】拷贝和移动语义
#include <iostream> using namespace std; //编译选项:g++ -std=c++11 test0.cpp -fno-elide-constructors class Test
{
public:
int* m; //所涉及的内存资源 //用于统计构造函数、析构函数和拷贝构造等函数被调用的次数
static int n_ctor;
static int n_dtor;
static int n_cptor;
static int n_mctor; public:
Test() : m(new int())
{
cout <<"Construct: "<< ++n_ctor << endl;
} Test(const Test& t) : m(new int(*t.m))
{
cout <<"Copy Construct: "<< ++n_cptor << endl;
} //移动构造函数
/*
Test(Test&& t) : m(t.m)
{
t.m = nullptr;
cout <<"move Construct: "<< ++n_mctor << endl;
}
*/ ~Test()
{
cout <<"Destruct: " << ++n_dtor <<endl;
}
}; int Test::n_ctor = ;
int Test::n_dtor = ;
int Test::n_cptor = ;
int Test::n_mctor = ; Test getTemp()
{
Test t;
cout <<"Resource from: " << __func__ << ": " << hex << t.m << endl;
return t; //编译器会尝试以下几种方式进行优化。(见后面的RVO优化部分的讲解)
//1. 优先用NRVO优化。
//2. std::move(Test()),再调用move constructors
//3. 如果以上两者均失败,则调用copy constructors;
} //高性能的Swap函数
template<typename T>
void swap(T& a, T& b)
{
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
} int main()
{
/*实验时,可以通过注释和取消注释移动构造函数两种方式来对比*/
Test t = getTemp();
cout <<"Resource from: " << __func__ << ": " << hex << t.m << endl; return ;
}
/*实验结果:
1. 存在移动构造函数时
Construct: 1
Resource from: getTemp: 0x3377f0
move Construct: 1
Destruct: 1
move Construct: 2
Destruct: 2
Resource from: main: 0x3377f0
Destruct: 3 2. 不存在移动构造函数时
Construct: 1
Resource from: getTemp: 0x5477f0
Copy Construct: 1
Destruct: 1
Copy Construct: 2
Destruct: 2
Resource from: main: 0x547810
Destruct: 3 以上实验结果表明,虽然调用拷贝构造或移动构造的次数没有减少,但由于
拷贝构造涉及内存的拷贝,而移动构造只是资源的转移效率会更高。
*/
(2)移动语义
①临时对象的产生和销毁以及拷贝的发生对于程序员来说基本上是透明的,不会影响程序的正确性,但可能影响程序的执行效率且不易被察觉到。
②移动语义则是通过“偷”内存的方式,将资源从一个对象转移到另一个对象身上,由于不存在内存拷贝,其效率一般要高于拷贝构造。
【编程实验】std::move与移动语义
#include <iostream>
using namespace std; //编译选项:g++ -std=c++11 test2.cpp -fno-elide-constructors //资源类(假设该类占用内存资源较多)
class HugMem
{
public:
HugMem(){cout <<"HugMem()" << endl;}
HugMem(const HugMem&){cout <<"HugMem(const HugMem&)" << endl; }
~HugMem(){cout <<"~HugMem()" << endl;};
}; class Test
{
private:
HugMem* hm;
public:
Test()
{
hm = new HugMem();
cout << "Test Constructor" << endl;
} Test(const Test& obj): hm (new HugMem(*obj.hm))
{
cout << "Test Copy Constructor" << endl;
} //移动构造函数
Test(Test&& obj) : hm(obj.hm) //资源转移
{
obj.hm = nullptr; //让出资源的所有权
cout << "Test Move Constructor" << endl;
} Test& operator=(const Test& obj)
{
if(this != &obj){
hm = new HugMem(*obj.hm);
cout << "operator=(const Test& obj)" << endl;
}
return *this;
} Test& operator=(Test&& obj)
{
if(this != &obj){
hm = obj.hm;
obj.hm = nullptr;
cout << "operator=(const Test&& obj)" << endl;
}
return *this;
} ~Test()
{
delete hm;
//cout <<"~Test()" << endl;
}
}; Test getTest()
{
Test tmp; return tmp; //这里可能会被编译器优化,见返回值优化部分
} int main()
{
Test t1; cout << "===============================================" << endl; Test t2(t1); //拷贝构造 cout << "===============================================" << endl;
Test t3(std::move(t2)); //移动构造 cout << "===============================================" << endl;
t3 = getTest();//移动赋值 t1 = t3; //拷贝赋值 cout << "===============================================" << endl;
Test t4 = getTest(); //从临时对象->t4,调用移动构造,然后临时对象销毁
cout << "===============================================" << endl;
Test&& t5 = getTest(); //t5直接将临时对象接管过来,延长了其生命期
//注意与t4的区别 return ;
}
/*输出结果:
e:\Study\C++11\15>g++ -std=c++11 test2.cpp -fno-elide-constructors
e:\Study\C++11\15>a.exe
HugMem()
Test Constructor
===============================================
HugMem(const HugMem&)
Test Copy Constructor
===============================================
Test Move Constructor
===============================================
HugMem()
Test Constructor
Test Move Constructor
operator=(const Test&& obj)
HugMem(const HugMem&)
operator=(const Test& obj)
===============================================
HugMem()
Test Constructor
Test Move Constructor
Test Move Constructor
===============================================
HugMem()
Test Constructor
Test Move Constructor
~HugMem()
~HugMem()
~HugMem()
~HugMem()
*/
(3)其它问题
①移动语义一定是要修改临时对象的值,所以声明移动构造时应该形如Test(Test&&),而不能声明为Test(const Test&&)。
②默认情况下,编译器会隐式生成一个移动构造函数,而如果自定义了拷贝构造函数、拷贝赋值函数、移动赋值函数、析构函数中的任何一个或多个,编译器就不会再提供默认的默认的版本。
③默认的移动构造函数实际上跟默认的拷贝构造函数是一样的,都是浅拷贝。通常情况下,如果需要移动语义,必须自定义移动构造函数。
④在移动构造函数是抛出异常是危险的。因为异常抛出时,可能移动语义还没完成,这会导致一些指针成为悬挂指针。可以为其添加一个noexcept关键字以保证移动构造函数中抛出来的异常会直接调用terminate来终止程序。
3. RVO/NRVO和std::move
(1)RVO/NRVO返回值优化:是编译器的一项优化技术,它的功能主要是消除为保存函数返回值而创建的临时对象。
class X
{
public:
X(){cout <<"constructor..." << endl;}
X(const X& x){cout <<"copy constructor" << endl;}
}; //1、按值返回匿名的临时对象(RVO)
X func()
{
return X(); //RVO优化
} //2、按值返回具名局部对象(NRVO)
X func()
{
X x; //返回方式1:
return x; //按值返回具名对象:NRVO优化(编译器自动优化,效率高!) //返回方式2:(不要这样做!)
//return std::move(x);//x转为右值,本意是通过调用move constructor来避开move constructor来提高效率。但实际上,
//效率比return x更低,因为后者会被编译器默认地采用更高效的NRVO来优化。
} //NRVO伪代码:
void func(X& result) //注意多了一个参数,修改了函数原型
{
//编译器所产生的default constructor的调用
result.X::X(); //C++伪代码,调用X::X() return;
} X xs = func();
(2)std::move与RVO的关系
①编译器会尽可能使用RVO和NRVO来进行优化。由于std::move(localObj)的返回值类型带有引用修饰符,反而不满足标准中的RVO条件,这样编译器只能选择move constructor。
②当无法使用RVO优化时,由于按值返回的是个右值,编译器会隐式调用std::move(localobj)来转换,从而尽力调用move constructor。
③如果以上这些都失败了,编译器才会调用copy constructor。
第15课 右值引用(2)_std::move和移动语义的更多相关文章
- 第16课 右值引用(3)_std::forward与完美转发
1. std::forward原型 template <typename T> T&& forward(typename std::remove_reference< ...
- C++ 11中的右值引用以及std::move
看了很多篇文章,现在终于搞懂了C++ 中的右值以及std::move 左值和右值最重要的区别就是右值其实是一个临时的变量 在C++ 11中,也为右值引用增加了新语法,即&& 比 ...
- C++ 11 右值引用以及std::move
转载请注明出处:http://blog.csdn.net/luotuo44/article/details/46779063 新类型: int和int&是什么?都是类型.int是整数类型,in ...
- C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward
这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运行测试过的,希望能用这些帮助理 ...
- Item 25: 对右值引用使用std::move,对universal引用则使用std::forward
本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 右值引用只能绑定那些有资格被move的对象上去.如 ...
- C++11右值引用和std::move语句实例解析
关键字:C++11,右值引用,rvalue,std::move,VS 2015 OS:Windows 10 右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一.从实践 ...
- 右值引用和std::move函数(c++11)
1.对象移动 1)C++11新标准中的一个最主要的特性就是移动而非拷贝对象的能力 2)优势: 在某些情况下,从旧内存拷贝到新内存是不必要的,此时对对象进行移动而非拷贝可以提升性能 有些类如IO类或un ...
- 透彻理解C++11新特性:右值引用、std::move、std::forward
目录 浅拷贝.深拷贝 左值.右值 右值引用类型 强转右值 std::move 重新审视右值引用 右值引用类型和右值的关系 函数参数传递 函数返还值传递 万能引用 引用折叠 完美转发 std::forw ...
- C++11中的右值引用及move语义编程
C++0x中加入了右值引用,和move函数.右值引用出现之前我们只能用const引用来关联临时对象(右值)(造孽的VS可以用非const引用关联临时对象,请忽略VS),所以我们不能修临时对象的内容,右 ...
随机推荐
- java_生态环境
Which Java package do I need? Software Developers: JDK (Java SE Development Kit). For Java Developer ...
- Java参数验证Bean Validation 框架
1.为什么要做参数校验? 参数校验和业务逻辑代码分离,参数校验代码复用,统一参数校验方式.校验不太通过时统一异常描述. 2.bean validation规范 JSR303 规范(Bean Valid ...
- 设计一个 硬件 实现的 Dictionary(字典)
Dictionary 就是 字典, 是一种可以根据 Key 来 快速 查找 Value 的 数据结构 . 比如 我们在 C# 里用到的 Dictionary<T>, 在 程序设计 里, 字 ...
- Percona XtraDB Cluster高可用与状态快照传输(PXC 5.7 )
Percona XtraDB Cluster(下称PXC)高可用集群支持任意节点在运行期间的重启,升级或者意外宕机,即它解决了单点故障问题.那在这个意外宕机或者重启期间,该节点丢失的数据如何再次进行同 ...
- centos7下git服务器端搭建(转)
git的安装: yum 源仓库里的 Git 版本更新不及时,最新版本的 Git 是 1.8.3.1,但是官方最新版本已经到了 2.9.2.想要安装最新版本的的 Git,只能下载源码进行安装. 1. 查 ...
- Hadoop HDFS的shell(命令行客户端)操作实例
HDFS的shell(命令行客户端)操作实例 3.2 常用命令参数介绍 -help 功能:输出这个命令参数手册 -ls 功能:显示目录信息 示例: hadoop fs ...
- chmod命令详解
Linux chmod命令 Linux/Unix 的文件调用权限分为三级 : 文件拥有者.群组.其他.利用 chmod 可以藉以控制文件如何被他人所调用. 使用权限 : 所有使用者 语法: chmod ...
- Ribbon Ping机制
在负载均衡器中,提供了 Ping 机制,每隔一段时间,会去 Ping 服务器,判断服务器是否存活,该工作由 com.netflix.loadbalancer.IPing 接口的实现类负责,如果单独使用 ...
- Delphi调用DLL中的接口
问题描述: 具体问题就是在隐式使用接口变量后,在FreeLibrary执行后,就会出现一个非法访址的错误. 这个错误的原因就是在FreeLibrary后,DLL以的代码均为不可用状态,而在代码执行完整 ...
- CentOS6.5把MySQL从5.1升级到5.6后,MySQL不能启动
解决了:进入mysql安装目录 cd /var/lib/mysql删除了如下三个文件:ibdata1 ib_logfile0 ib_logfile1 CentOS6.5把MySQL从5.1升级到5 ...