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和移动语义的更多相关文章

  1. 第16课 右值引用(3)_std::forward与完美转发

    1. std::forward原型 template <typename T> T&& forward(typename std::remove_reference< ...

  2. C++ 11中的右值引用以及std::move

    看了很多篇文章,现在终于搞懂了C++ 中的右值以及std::move   左值和右值最重要的区别就是右值其实是一个临时的变量 在C++ 11中,也为右值引用增加了新语法,即&&   比 ...

  3. C++ 11 右值引用以及std::move

    转载请注明出处:http://blog.csdn.net/luotuo44/article/details/46779063 新类型: int和int&是什么?都是类型.int是整数类型,in ...

  4. C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward

    这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运行测试过的,希望能用这些帮助理 ...

  5. Item 25: 对右值引用使用std::move,对universal引用则使用std::forward

    本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 右值引用只能绑定那些有资格被move的对象上去.如 ...

  6. C++11右值引用和std::move语句实例解析

    关键字:C++11,右值引用,rvalue,std::move,VS 2015 OS:Windows 10 右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一.从实践 ...

  7. 右值引用和std::move函数(c++11)

    1.对象移动 1)C++11新标准中的一个最主要的特性就是移动而非拷贝对象的能力 2)优势: 在某些情况下,从旧内存拷贝到新内存是不必要的,此时对对象进行移动而非拷贝可以提升性能 有些类如IO类或un ...

  8. 透彻理解C++11新特性:右值引用、std::move、std::forward

    目录 浅拷贝.深拷贝 左值.右值 右值引用类型 强转右值 std::move 重新审视右值引用 右值引用类型和右值的关系 函数参数传递 函数返还值传递 万能引用 引用折叠 完美转发 std::forw ...

  9. C++11中的右值引用及move语义编程

    C++0x中加入了右值引用,和move函数.右值引用出现之前我们只能用const引用来关联临时对象(右值)(造孽的VS可以用非const引用关联临时对象,请忽略VS),所以我们不能修临时对象的内容,右 ...

随机推荐

  1. ipfs webui 管理界面

    ipfs 内置了一个webui 默认的端口是5001 访问地址 http://ip:5001/webui 环境准备 docker-compose 文件   version: "3" ...

  2. 几个OOD概念

    Composition vs. Aggregation Composition和Aggregation都是”包含”的关系 (part of, made up of) ,不同的是生命周期.对于Compo ...

  3. 13机器学习实战之PCA(1)

    降维技术 对数据进行降维有如下一系列的原因: 使得数据集更容易使用 降低很多算法的计算开销 去除噪音 使得结果易懂 在以下3种降维技术中, PCA的应用目前最为广泛,因此本章主要关注PCA. 主成分分 ...

  4. 原码,反码,补码,及Java中数字表示方法

    原码:原码是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 如:如果是八位二进制1即用00000001表示,-1即用10000001表示. 反码:正数的反码就是其本身,负数的反码是在其 ...

  5. Speeding Up The Traveling Salesman Using Dynamic Programming

    Copied From:https://medium.com/basecs/speeding-up-the-traveling-salesman-using-dynamic-programming-b ...

  6. C# .NET 2.0 判断当前程序进程是否为64位运行时 (x64)

    public static bool Is64BitProcess() { ; }

  7. C# DataAdapter.Update() 无法更新数据表中删除的数据行

    用DataAdapter.Update() 方法更新删除了部分DataRow 的 DataTable .但是数据库中的数据没有随着更新而变化. 原因:DataTable 删除 DataRow 时,使用 ...

  8. docker-compose使用volume部署mysql时permission deny问题解决

    问题整体情况为使用docker做mysql的容器,然后结合其他服务一起通过docker-compose启动,并且为了一次性建表和设置用户权限我又在mysql中封装了setup.sh.schema.sq ...

  9. C#如何HttpWebRequest模拟登陆,获取服务端返回Cookie以便登录请求后使用

    public static string GetCookie(string requestUrlString, Encoding encoding, ref CookieContainer cooki ...

  10. <亲测>阿里云centos7 挂载数据盘配置

    阿里云centos7 挂载数据盘配置 2018年07月17日 15:13:53 阅读数:235更多 个人分类: linux阿里云ECS数据盘挂载   查看磁盘情况 fdisk -l  其中/dev/v ...