本文大部分来自这里,并不是完全着行翻译,如有不明白的地方请参考原文。

在c++中,创建临时对象的开销对程序的影响一直很大,比如以下这个例子:

String getName(){
return “Kian”;
}
string name = getName();

name对象的构建可以细分为3步:

1. 用kian构建函数内的局部string对象tmp1

2. 调用复制构造函数将tmp1复制到tmp2,并析构tmp1.

3. 调用赋值拷贝函数将tmp2拷贝到name,并析构tmp2。

所以一共做了3次内存分配,两次复制拷贝操作,但是tmp1和tmp2都马上析构了,如果内存分配很大的话,这里的资源浪费是很可观的。在c++11之前,编译器已经会做一些优化了,比如返回值优化RVO(return value optimization)优化了第二步,省略了构建tmp2的开销,但是第3步直到c++11引入移动语义后才得到了彻底解决。移动语义依赖于右值引用实现,要了解移动语义,必须先要明白什么是右值和左值。

什么是右值和左值

c++11中存在右值和左值。左值可以取地址,是相对永久的对象,可以被赋值,比如

int a;
a=; //a is a lvalue

左值也可以不是变量,如

int x;
int& getRef ()
{
return x;
}
getRef() = ;

相应地,右值是一个临时对象,不可以取地址。

int x;
int getRef ()
{
return x;
}
getRef();

getRef()的值是个右值,它不是x的引用,而是x的拷贝,是一个临时存在的对象。

右值引用和临时对象

在c++11之前,可以使用const引用绑定到临时对象上,

const string& name = getName(); // ok
string& name = getName(); // NOT ok

不可以改变一个即将消失的对象,所以将非const引用绑定到临时对象上是不允许的。到了c++11,引进了右值引用&&,

const string&& name = getName(); // ok
string&& name = getName(); // also ok - praise be!

左值和右值最重要的区别在于做为函数参数时,

printReference (const String& str)
{
cout<<str;
}
printReference (String&& str)
{
cout<<str;
}

前者可以接受任何参数,而后者只可以接收右值,及临时对象,

string me( "alex" );
printReference( me ); // calls the first printReference function, taking an lvalue reference
printReference( getName() ); // calls the second printReference function, taking a mutable rvalue reference

移动构造函数和赋值符

移动构造函数接收一个临时对象作为参数,直接获取临时对象内部资源,避免重新分配内存。

假设我们有这样一个简单的ArrayWrapper类:

class ArrayWrapper
{
public:
ArrayWrapper (int n)
_p_vals( new int[ n ] ),_size( n )
{ }
// copy constructor
ArrayWrapper (const ArrayWrapper& other)
: _p_vals( new int[ other._size ] ), _size( other._size )
{
for ( int i = ; I < _size; ++i )
{
_p_vals[ i ] = other._p_vals[ i ];
}
}
~ArrayWrapper ()
{
delete [] _p_vals;
}
private:
int *_p_vals;
int _size;
};

可以看出复制构造函数每次都需要分配内存并着个赋值,这是非常消耗资源的,我们加一个移动构造函数,

// move constructor
ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals ), _size( other._size )
{
other._p_vals = NULL;
other._size = ;
}

移动构造函数比复制构造函数简单多了,不过要注意两点,

1. 参数必须是非const右值引用

2. 必须将other._p_vals设为NULL

参数不设为非const,就不能将other._p_vals设为NULL,为什么要设为NULL?,因为 other是一个即将消失的右值,调用其析构函数会释放_p_vals指向的内存,不设置为NULL,我们得到的对象就会指向垃圾内存。

因为参数是非const的,不能接收const右值,所以千万不要这样返回需要使用的右值。

const ArrayWrapper getArrayWrapper (); // makes the move constructor useless, the temporary is const!

如果对象内部包含另一个对象,移动构造函数内会发生什么?假设ArrayWrapper包含的不只有_size,而是更详细的数据,比如

class MetaData
{
public:
MetaData (int size, const std::string& name)
: _name( name ), _size( size )
{}
// copy constructor
MetaData (const MetaData& other)
: _name( other._name ), _size( other._size )
{}
// move constructor
MetaData (MetaData&& other)
: _name( other._name ), _size( other._size )
{}
std::string getName () const { return _name; }
int getSize () const { return _size; }
private:
std::string _name;
int _size;
};

那么ArrayWrapper需要修改成这样,

class ArrayWrapper
{
public:
// default constructor produces a moderately sized array
ArrayWrapper ()
: _p_vals( new int[ ] ), _metadata( , "ArrayWrapper" )
{}
ArrayWrapper (int n)
: _p_vals( new int[ n ] ), _metadata( n, "ArrayWrapper" )
{}
// move constructor
ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals ), _metadata( other._metadata )
{
other._p_vals = NULL;
}
// copy constructor
ArrayWrapper (const ArrayWrapper& other)
: _p_vals( new int[ other._metadata.getSize() ] ), _metadata( other._metadata )
{
for ( int i = ; i< _metadata.getSize(); ++i )
{
_p_vals[ i ] = other._p_vals[ i ];
}
}
~ArrayWrapper ()
{
delete [] _p_vals;
}
private:
int *_p_vals;
MetaData _metadata;
};

当 ArrayWrapper调用移动构造函数时,_metadata调用的是移动构造函数还是复制构造函数?表面看应该是移动构造函数,因为ArrayWrapper的移动构造函数参数other 是右值引用,但要注意的是,右值引用不是右值!右值引用是一个左值,other._metadata也是一个左值,所以_metadata调用的是复制构造函数。

Std::move

怎样让_metadata也调用移动构造函数,我们需要使用std::move,move并不移动任何东西,只是将对象转换为右值。使用move后,代码就是这样的,

// move constructor
ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals ) , _metadata( std::move( other._metadata ) )
{
other._p_vals = NULL;
}
MetaData (MetaData&& other)
: _name( std::move( other._name ) )
: _size( other._size )
{}

move的功能很神奇吧,它是用什么新技术将对象转为右值的呢?事实是它用的是c++一直都有的static_cast转换符。下面是它的源码,

template <typename _Tp>
<P>inline typename ;std::remove_reference<_Tp>:::type&&
move(_Tp&&& __t)
{return static_cast<typename std::remove_reference<_Tp>::type&&> (__t);}

看到它的第一感觉是它应该只能接收右值引用啊,为什么左值没有问题?

String s1(“kian”), s2;
s2 = std:move(string(“zhang”)); //rvalue,right
s2 = std:move(s1); //lvalue, right

通常我们不能将右值引用绑定到左值上,不过为了支持move语义,c++11定义了两个例外:

1. 当将左值引用传递给函数的右值引用参数,且此右值引用指向模板类型参数(如_Tp&&),编译器推断模板类型参数为实参的左值引用类型。即在std:move(s1)中,

_Tp推断为string&,那么string& &&又是什么?这由第二条确定例外定义

2. 引用的引用形成折叠,x& &,x& &&, x&& &都折叠成x&;只有x&& &&折叠成x&&。

所以std:move(s1)会实例化

string&& move(string& t)

而std:move(string(“zhang”))实例化

string&& move(string&& t)

参考:

http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html

http://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization

c++11之右值引用的更多相关文章

  1. C++11之右值引用(二):右值引用与移动语义

    上节我们提出了右值引用,可以用来区分右值,那么这有什么用处?   问题来源   我们先看一个C++中被人诟病已久的问题: 我把某文件的内容读取到vector中,用函数如何封装? 大部分人的做法是: v ...

  2. c++11的右值引用、移动语义

    对于c++11来说移动语义是一个重要的概念,一直以来我对这个概念都似懂非懂.最近翻翻资料感觉突然开窍,因此记下.其实搞懂之后就会发现这个概念很简单,并无什么高深的地方. 先说说右值引用.右值一般指的是 ...

  3. C++11 的右值引用

    作者:Tinro链接:https://www.zhihu.com/question/22111546/answer/30801982来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  4. [转载]如何在C++03中模拟C++11的右值引用std::move特性

    本文摘自: http://adamcavendish.is-programmer.com/posts/38190.htm 引言 众所周知,C++11 的新特性中有一个非常重要的特性,那就是 rvalu ...

  5. C++11之右值引用(一):从左值右值到右值引用

    C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是 ...

  6. 【C/C++开发】C++11:右值引用和转发型引用

    右值引用 为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念.右值引用采用T&&这一语法形式,比传统的引用T&(如 ...

  7. C++ 11的右值引用

    目录 一.问题导入 二.右值和右值引用 2.1 左值(lvalue)和右值(rvalue) 2.2 左值引用和右值引用 总结 参考资料 C++11 引入了 std::move 语义.右值引用.移动构造 ...

  8. C++11特性-右值引用

    什么是左值,什么是右值 常见的误区有 = 左边的是左值,右边的是右值. 左值:具有存储性质的对象,即lvalue对象,是指要实际占用内存空间.有内存地址的那些实体对象,例如:变量(variables) ...

  9. C++11之右值引用(三):使用C++11编写string类以及“异常安全”的=运算符

    前面两节,说明了右值引用和它的作用.下面通过一个string类的编写,来说明右值引用的使用. 相对于C++98,主要是多了移动构造函数和移动赋值运算符. 先给出一个简要的声明: class Strin ...

随机推荐

  1. ios上比较好用的Cydia插件

    1.iFile查看系统文件 2.KuaiDial归属地数据库 3.KuaiDial电话拨号助手 4.搜狗输入法 Photo Editor 房贷计算器

  2. cisco路由基于策略的路由选择

    cisco路由基于策略的路由选择 基于策略的路由选择是一种手段,通过它管理员可以在基于目的地的路由选择协议中实现偏离标准路由的路由选择.基于目的地的路由选择协议将根据到一个目的地的最短路径选择路由,基 ...

  3. js的Touch事件

    js的touch事件,一般用于移动端的触屏滑动$(function(){document.addEventListener("touchmove", _touch, false); ...

  4. Android 高级UI设计笔记15:HorizontalScrollView之 实现画廊式图片浏览器

    1. HorizontalScrollView 本来,画廊式的图片浏览器,使用Android中的Gallery就能轻松完成,但是Google说Gallery每次切换图片时都要新建视图,造成太多的资源浪 ...

  5. 【阿里云产品公测】消息队列服务MQS java SDK 机器人应用初体验

    [阿里云产品公测]消息队列服务MQS java SDK 机器人应用初体验 作者:阿里云用户啊里新人   初体验 之 测评环境 由于MQS支持外网访问,因此我在本地做了一些简单测试(可能有些业余),之后 ...

  6. iOS - 网络 - NSURLSession

    1.NSURLSession基础 NSURLConnection在开发中会使用的越来越少,iOS9已经将NSURLConnection废弃,现在最低版本一般适配iOS,所以也可以使用.NSURLCon ...

  7. Oracle基础 物理备份 冷备份和热备份(转)

    一.冷备份介绍:    冷备份数据库是将数据库关闭之后备份所有的关键性文件包括数据文件.控制文件.联机REDO LOG文件,将其拷贝到另外的位置.此外冷备份也可以包含对参数文件和口令文件的备份,但是这 ...

  8. 文本替换sed+字段处理cut,join+awk重新编排字段

    [1]sed工具(Stream Editor)--流编辑器 sed 本身也是一个管线(管道)命令,可以分析 standard input 的啦! 而且 sed 还可以将数据进行取代.删除.新增.截取特 ...

  9. 编译cordova-android代码

    开发cordova-android的插件得引入cordova的jar包,网上打包好的参差不齐,也不一定是最新版本,下面我们自己打包 cordova的jar包. 1.clone代码 cordova-an ...

  10. 剑指Offer11 在O(1)内删除链表结点

    /************************************************************************* > File Name: 11_Delete ...