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

在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. UICollectionViewController

    UICollectionViewController 目录 概述 UICollectionView UICollectionViewCell 代理方法 详细细节 概述 UICollectionView ...

  2. Foundation框架之NSString及其Mutable类型

    Foundation框架之NSString及其Mutable类型 目录 概述 对字符串的实用操作 拼接 拆分 字符串比较 是否包含某字符串 字数统计 大小写转换 具体的方法参见API 待研究 概述 对 ...

  3. APP快速通过苹果AppStore审核九大诀窍

    [IT168技术]对于移动应用开发者来说, 最令人沮丧的可能莫过于辛辛苦苦开发的应用, 没能通过苹果AppStore的审核,或者在应用更新时遭遇下架.苹果的AppStore的审核流程和标准, 一向不透 ...

  4. QT 操作数据库

    整理一下 QT 操作数据库的一些要点,以备以后的查询学习(主要是操作 mysql ). 首先,要查询相关的驱动是否已经装好了,可以用以下的程序进行验证: #include <QtCore/QCo ...

  5. 关于修改Android手机的音量

    首先,必须要获取系统的声音服务权限 <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS&qu ...

  6. LeetCode44 Wildcard Matching

    题目: Implement wildcard pattern matching with support for '?' and '*'. '?' Matches any single charact ...

  7. A+B Coming

    Problem Description Many classmates said to me that A+B is must needs.If you can’t AC this problem, ...

  8. ios-为银行卡号格式化 每隔四位添加一个空格

    -(NSString *)formatterBankCardNum:(NSString *)string { NSString *tempStr=string; NSInteger size =(te ...

  9. 【阿里云产品公测】阿里云ACE配置全程图解,详细到不行!

    作者:阿里云用户sofia 看过阿里云社区的其他技术大姥们的评测教程,感觉还是不够详细,对于一个第一次接触ace.新浪sae这类的应用来说还是比较陌生的.我最喜欢写教程了,不过我有我的风格,那就是简单 ...

  10. VS2013 添加文件头部注释模板

    在看视频的时候发现,视频中每次新建一个类文件 都会自动生成一串 头部的注释: