转载请注明出处:http://blog.csdn.net/luotuo44/article/details/46779063

新类型:

int和int&是什么?都是类型。int是整数类型,int&则是整数引用类型。相同int&&也是一个类型。两个引號&&是C++ 11提出的一个新的引用类型。记住,这是一个新的类型。默念10次吧。假设你记住这个新类型,那么非常多疑问都能迎刃而解。而且对《Effective Modern C++》说到的void
f(Widget&& w)。就非常easy明确w是新类型的一个值。肯定是一个左值而不是右值,自然就不用去翻第二页了。

出现了新类型。就像定义一个新类一样。自然有两件事接着要做:怎样初始化、函数匹配(依据參数类型匹配函数)。先看后者。

void fun(int &a)
{
cout<<"int &a "<<a<<endl;
} void fun(int &&a)
{
cout<<"int &&a "<<a<<endl;
} int main()
{
int b = 3;
fun(b); return 0;
}

main函数中的fun(a)会匹配第一个fun函数。由于第二个fun的參数是int右值引用。不能匹配一个左值。值得注意的是,尽管第二个fun函数的a的类型是右值引用类型,但它却是一个左值,由于它是某一个类型变量嘛。

那要怎么做才干使得b匹配第二个fun函数呢?强制类型转换,把b强制转换成右值引用类型,也就是使用static_cast<int&&>(b)。此时,自然就会匹配第二个fun函数了。

在C++ 11中。static_cast<T&&>有一个高大上的替代物std::move。事实上。高大上的std::move做的事情和前面说的差点儿相同。强制类型转换使得匹配特定的函数而已。

右值引用和std::move引以自豪的高效率又是怎么实现的呢?本文从经典的拷贝构造函数说起,但样例却不经典。

class Test
{
public:
Test() : p(nullptr) {}
~Test() { delete [] p; } Test(Test &t) : p(t.p)//注意这个拷贝构造函数的參数没有const
{
t.p = nullptr;//不然会在析构函数中,delete两次p
} private:
char *p;
}; int main()
{
Test a;
Test b(a);
return 0;
}

注意这个拷贝构造函数的參数没有const。

读者们,你们会认为上面那个Test在拷贝构造函数不高效吗?差点儿是没有不论什么效率上的负担啊。

类似,也能写一个高效的赋值函数。

可是,一般来说我们的拷贝构造函数的參数都是有const的。有const意味着不能改动參数t。上面的代码也能够看到:将t.p赋值nullptr是必须的。

由于t.p不能改动,所以不得不进行深复制。不然将出现经典的浅复制问题。不用说。有const的拷贝构造函数更适合一些,毕竟我们须要从一个const对象中复制一份。

移动构造:

性能的救赎:

在C++ 11之前,我们仅仅能眼睁睁看着重量级的类仅仅能调用有const的拷贝构造函数,复制一个重量级对象。在C++ 11里面增加了一个新类型右值引用,那能不能用这个右值引用类型作为构造函数的參数呢?当然能够啦。毕竟类的构造函数參数没有什么特别的要求。

习惯上,我们会称这种构造函数为移动(move)构造函数,相应的赋值操作则称为移动(move)赋值函数。

他们的代码也非常easy。例如以下:

class Test
{
public:
Test() : p(nullptr)
{
cout<<"constructor"<<endl;
} ~Test()
{
cout<<"destructor"<<endl;
delete [] p;
} Test(const Test& t) : p(nullptr), str(t.str)
{
cout<<"copy constructor"<<endl;
if(t.p != nullptr)
{
p = new char[strlen(t.p)+1];
memcpy(p, t.p, strlen(t.p)+1);
} } Test& operator = (const Test& t)
{
cout<<"operator = "<<endl;
if( this != &t )
{
char *tmp =nullptr;
if( t.p != nullptr)
{
tmp = new char[strlen(t.p)+1];
memcpy(tmp, t.p, strlen(t.p)+1);
}
delete [] p;
p = tmp;
str = t.str;
} return *this;
} Test(Test && t)noexcept : p(t.p), str(std::move(t.str))//怎样移动由string类完毕
{
cout<<"move copy constructor"<<endl;
t.p = nullptr;//记得。不然会对同一段内存反复delete
} Test& operator = (Test &&t)noexcept
{
cout<<"move operator ="<<endl;
if( this != &t)
{
p = t.p;
t.p = nullptr; str = std::move(t.str);//怎样移动由string类完毕
} return *this;
} private:
char *p;
std::string str;
};

协助完毕移动构造:

有了move构造函数和move赋值函数。下一步是协助完毕移动构造/移动赋值。包含程序猿和编译器。假设不协助的话,可能调用的是copy构造函数而不是move构造函数。从前文也能够看到,协助完毕移动构造/移动赋值,事实上也就是使得在函数调用时能匹配參数为右值引用的函数。码农能做的就是强制将一个不须要了的对象调用std::move。

如以下代码:

int main()
{
Test a;
Test b = std::move(a);//调用move构造函数
Test c = a;//调用copy构造函数
return 0;
}

尽管上面的代码在构造b的时候调用了移动构造。但明显上面代码一点都不正常,为什么不直接构造b呢?全然用不着move构造啊。此时可能有读者会想到这样一个用途:我们能够为一个暂时对象加上std::move啊,比方operator + 的返回值。实际上这是画蛇添足的。

由于编译器会为这个暂时对象当作右值(准确说应该是:将亡值),当然也就自己主动能使用移动构造了。

难道移动构造是屠龙之技?不是的。

移动构造的一大长处是能够高效地在函数中返回一个重量级的类。函数返回值会在后面说到。

除了在函数返回值用到外,在函数内部也能够使用到的。

std::vector<std::string> g_ids;//全局变量
void addIds(std::string id)
{
g_ids.push_back(std::move(id));
} int main()
{
addIds("1234");//在加入到g_ids过程中,会调用一次copy构造函数。一次move构造函数
std::string my_id = "123456789";
addIds(my_id);//会调用一次copy构造函数,一次move构造函数 for(auto &e : g_ids)
cout<<e<<endl; return 0;
}

有读者可能会问,为什么addIds的參数不是const std::string &的形式,这样在对my_id调用的时候就不用为參数id调用一次copy构造函数。

但别忘了,此时id被push进g_ids时就要必需要调用一次copy构造函数了。

前面用红色标出,对一个不须要的了对象调用std::move强制类型转换。

为什么说是不须要了的呢?由于一个对象被std::move而且作为move构造函数的參数后,该对象所占用的一些资源可能被移走了。留下一个没实用的空壳。注意。尽管是空壳。但在移动的时候,也要保证这个空壳对象能正确析构。

也许读者还是认为移动语义是屠龙之技,那么读者们想一下:vector容器在扩容的时候吧。

有了移动语义,vector里面的对象从旧地址搬到新地址,毫不费劲。

右值引用情况下的返回值问题:

有了右值引用,读者可能会写出以下的代码:

Test&& fun()
{
Test t;
...
return std::move(t);
} int main()
{
Test && tt = fun();//和下者,哪个才是正确的呢?
Test tt = fun();//和上者,哪个才是正确的呢? return 0;
}

无疑,在main函数中,还须要考虑一下tt对象是一个Test类型还是Test&&类型。事实上。大错早就在fun函数中铸成了。

返回的仅仅是一个引用,真身呢?真身已经在fun函数中被摧毁了。Meyers早在《Effective C++》里面就告诫过:不要在函数中返回一个引用。前文也已经说了。右值引用也是一个引用(类型)! 那返回什么好呢? 当然是真身啦!  如同以下代码:

Test fun()
{
Test t;
... return t;
} int main()
{
Test tt = fun();
return 0;
}

当函数返回一个对象时,编译器会将这个对象看作的一个右值(准确来说是将亡值)。所以无需在fun函数中。将return t写成return std::move(t);

当然。实际上t变量的真身还是在fun函数中被摧毁了,但真身里面有价值的东西都被移走了。

对!就像比克大魔王那样,临死前把自己的孩子留下来! 在C++里面。当然不能生成一个孩子,可是能够通过移动构造函数生成一个暂时对象。把有价值的东西移走。由于不是移动到main函数的tt变量中。仅仅是移动到了暂时对象。所以接下来暂时对象还要进行一次移动,把有价值的东西移动到main函数的tt变量中。这个移动过程无疑是一个非常好的金蝉脱壳的经典教程。读者能够执行一下代码,能够看到整个移动过程。

记住。用g++编译的时候要增加-fno-elide-constructors选项,禁止编译器使用RVO优化。

由于这里的RVO优化比移动构造更省力。

所以假设不禁用,会优先使用RVO,而非移动构造函数。

初始化:

由于右值引用也是一个引用类型。所以仅仅能初始化而不能赋值。既然这样。那仅仅需讨论什么类型的值才干用于初始化一个右值引用。一般来说,右值引用仅仅能引用右值、字面值、将亡值。所以问题转化为:什么是右值?网上介绍的一个方法是:要能不能将取地址符号&应用于某个标识符。假设能就说明它是一个左值,否则为右值。这种方法好像是行得通的。

只是,我认为没有必要分得那么清楚,又不是在考试。在寻常写代码时,没有谁会写类似a+++++a这种考试代码。我个人认为。记住最常见的那几种就差点儿相同了。

比方,字面量(1。‘c'这类),暂时(匿名)对象(即将亡值)。经过std::move()转换的对象,函数返回值。

其它的右值。还是留给编译器和Scott
Meyers吧。假设真的要细究,能够參考stackoverflow上的一个提问《What are rvalues, lvalues, xvalues, glvalues, and prvalues?

另一个问题须要说明。const的左值引用(const T&)是一个万能引用。既能够引用左值。也能引用右值。这个是非常特殊,特殊得非常自然。假设Test类未定义move构造函数。但用户又使用Test a = std::move(b)构造变量a。那么终于会调用Test类的copy构造函数。一个类的copy构造函数假设用户不定义。编译器会在必要情况下自己主动合成一个。所以上面的a变量肯定能构造。

慎重的编译器:

前一段貌似隐隐约约说到编译器不会自己主动合成一个move构造函数。是的。假设用户定义了copy构造函数。析构函数,operator =中的不论什么一个,编译器都不会自己主动为这个类合成一个move构成函数以及move 赋值函数。即使须要用到。详细的规则能够点这里。我个人觉得是由于。当定义了那四个函数中的不论什么一个,都能够觉得这个类不是nontrival的了。

想一下,在什么情况下我们是须要析构函数和copy构造函数的。

当这个类里面有一些资源(变量)须要我们手动管理的时候。既然有资源要管理。那么读者你认为编译器默认生成的move构造函数的内部实现应该是怎么样的呢?对类里面的全部成员都调用std::move进行移动?还是调用copy构造函数复制一份呢?这样的吃力但又不见得讨好的事情,编译器选择不干。毕竟还有前面说到的const T& 能够引用一个右值。没有move构造函数。copy构造函数顶上就可以。

作为类的设计者,你当然知道那些资源(变量)究竟是move还是copy。假设是move的话。那么直接用=default告诉编译器:别操心。直接用对全部变量move即可了。例如以下:

class Test
{
public:
Test() p(new int) {}
~Test()=default;
Test(const Test&)=delete;
Test& operator = (const Test&)=delete; Test(Test &&)=default;//告诉编译器
Test& operator = (Test &&)=default;//告诉编译器 private:
std::unique_ptr<int> p;
}

C++ 11 右值引用以及std::move的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

  7. C++11右值引用

    [C++11右值引用] 1.什么是左值?什么是右值? 左值是表达式结束后依然存在的对象:右值是表达式结束时就不再存在的对象. 2.std::move的作用是什么? std::move用于把任意类型转化 ...

  8. c++11 右值引用和移动语义

    什么是左值.右值 最常见的误解: 等号左边的就是左值,等号右边的就是右值 左值和右值都是针对表达式而言的, 左值是指表达式结束后依然存在的持久对象 右值是指表达式结束时就不再存在的临时对象区分: 能对 ...

  9. 关于C++11右值引用和移动语义的探究

    关于C++11右值引用和移动语义的探究

随机推荐

  1. 使用SHA1、SHA2双证书进行微软数字签名

    微软是第一个宣布了SHA-1弃用计划,在2016年之后Windows和IE将不再信任SHA-1证书.正好我们公司的数字签名也到期了,索性就重新申请了sha256和sha1的新数字证书,用来给产品签名. ...

  2. Python 数据分析(二 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识

    Python 数据分析(二) 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识 第1节 groupby 技术 第2节 数据聚合 第3节 分组级运算和转换 第4 ...

  3. cocos2d-x游戏开发系列教程-中国象棋05-开始游戏

    前情回顾 通过CCMainMenu的init函数,已经把所有的按钮,棋子都摆放完毕了,但是这个时候,棋子是不能走动的,只有在开始游戏之后才能移动棋子. 点击

  4. ZJOI2013 防守战线

    题目 战线可以看作一个长度为\(n\)的序列,现在需要在这个序列上建塔来防守敌兵,在序列第\(i\)号位置上建一座塔有\(C_i\)的花费,且一个位置可以建任意多的塔,费用累加计算.有\(m\)个区间 ...

  5. android apk打包之后js调用失效的解决办法

    现在android下应用开发的界面用html5+css3写,交互用javascript和java沟通,但是用上混淆后发现javascript调用java类定义的方法老说找不到这个方法.一番折腾后发现是 ...

  6. 总线接口与计算机通信(三)UART起止式异步通用串行数据总线

    串口简介 1. 什么是串口? 串口是计算机上一种非常通用的设备通信的协议.串口通信的概念非常简单,串口按位(bit) 发送和接收字节.尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送 ...

  7. Java获取随机数的几种方法

    Java获取随机数的几种方法 .使用org.apache.commons.lang.RandomStringUtils.randomAlphanumeric()取数字字母随机10位; //取得一个3位 ...

  8. 基于visual Studio2013解决C语言竞赛题之1034数组赋值

          题目 解决代码及点评 /********************************************************************** ...

  9. innerHeight与clientHeight、innerWidth与clientWidth、scrollLeft与pageXOffset等属性

    区分innerHeight与clientHeight.innerWidth与clientWidth.scrollLeft与pageXOffset等属性 标签: innerheight clienthe ...

  10. checkbox之checked的方法(attr和prop)区别

    1. $('#checkbox').click(function(){ if($('#checkbox').is(':checked')) { $(".sendmailhui"). ...