您的位置:首页 > 编程语言 > C语言/C++

返回局部变量如何避免拷贝:Move semantics and rvalue references in C++11

2015-12-31 16:10 531 查看
本文原文来自:http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html
另一篇很好的文章:https://www.ibm.com/developerworks/community/blogs/5894415f-be62-4bc0-81c5-3956e82276f3/entry/RVO_V_S_std_move?lang=en

一些概念:

lvalue(左值):可以出现在赋值操作的左边,可读可写。

rvalue(右值):可以出来再赋值操作右边,但不能用于左边的值,右值只能读而不能写。
object(对象):具有类型的一段内存区域,变量就是一个有名字的对象。
C++ has always produced fast programs. Unfortunately, until C++11,
there has been an obstinate wart that slows down many C++ programs: the creation of temporary objects. Sometimes these temporary objects can be optimized away by the compiler (the return value optimization, for example). But this is not always the case, and
it can result in expensive object copies. What do I mean?
C++ 代码一直以高效著称,不幸的是,在C++ 11版本之前,一直存在一个顽疾拖慢许多C++程序的执行:返回创建的临时对象,有时,编译器会优化对此优化(比如编译器的返回值优化模块:(named) Return Value Optimization (RVO/NRVO)),但并不总是这样。返回创建的局部对象有时会造成昂贵的拷贝开销,what
do i mean ? 先看下面一段代码。
Let's say that you have the following code:

#include <iostream>

usingnamespacestd;

vector<int> doubleValues (constvector<int>& v)
{
vector<int> new_values;
new_values.reserve(v.size());
for(auto itr = v.begin(), end_itr = v.end(); itr != end_itr; ++itr )
{
new_values.push_back( 2 * *itr );
}
return new_values;
}

int main()
{
vector<int> v;
for(inti = 0; i < 100; i++ )
{
v.push_back( i );
}
v = doubleValues( v );
}


If you've done a lot of high performance work in C++, sorry about the pain that brought on. If you haven't--well, let's walk through why this code is terrible C++03 code. (The rest of this tutorial will be about why
it's fine C++11 code.) The problem is with the copies. When doubleValues is called, it constructs a vector,
new_values, and fills it up. This alone might not be ideal performance, but if we want to keep our original vector unsullied, we need a second copy. But what happens when we hit the return statement?
如果你之前写过很多高性能的C++ 代码,看到上面的代码你肯定会很难受。我们来看看为什么这段C++ 03版本的代码很糟糕(后面会解释在C++ 11版本下却没问题)。问题在于拷贝操作,当doubleValues()函数被调用,它创建一个临时vector变量new_values,并用数据填充,仅仅是该操作也许就不是理想的操作,但是如果我们希望原始vector变量
v 保持整洁,那我们需要二次拷贝,当doubleValues()函数执行到return 语句时,会发生什么呢???

The entire contents of new_values must be copied! In principle, there could be up to two copies here: one into a temporary object to be returned, and a second when the vector assignment
operator runs on the line v = doubleValues( v );. The first copy may be optimized away by the compiler automatically, but there is no avoiding that the assignment to v will have to copy all the values again, which requires a new memory allocation and another
iteration over the entire vector.
new_values的整个内容将被复制,原则上,结果是总共产生两次拷贝:第一次,将v中的值每个*2后复制到new_values,第二次,赋值语句v = doubleValues(
v )又会产生一次拷贝。编译器也许会优化掉第一次拷贝(RVO/NRVO技术( (Nameed)
Return Value Optimization),参看资料第二篇推荐的文章),但却无法避开第二次拷贝,第二次拷贝又需要重新分配空间,并遍历整个vector.

This example might be a little bit contrived--and of course you can find ways to avoid this kind of problem--for example, by storing and returning the vector by pointer, or by passing in a vector to be filled up. The
thing is, neither of these programming styles is particularly natural. Moreover, an approach that requires returning a pointer has introduced at least one more memory allocation, and one of the design goals of C++ is to avoid memory allocations.
这个例子看起来也许有点牵强,当然你肯定有办法避免这类问题,例如,用指针来返回和保存vector变量(我的理解:doubleValues()函数中用new或malloc等为变量new_values在堆上申请空间,然后返回new_values的地址),或者直接传入一个vector变量用于填充。问题是,这两种编程风格都不是很优雅和漂亮,更进一步的,用指针来返回和保存vector变量势必会导致至少一次的内存申请(new或malloc),C++的编程原则之一就是尽量避免内存申请。

The worst part of this whole story is that the object returned from doubleValues is a temporary value that's no longer needed. When you have the line v = doubleValues( v ), the result
of doubleValues( v ) is just going to get thrown away once it is copied! In theory, it should be possible to skip the whole copy and just pilfer the pointer inside the temporary vector and keep it in v. In effect, why can't
we move the object? In C++03, the answer is that there was no way to tell if an object was a temporary or not, you had to run the same code in the assignment operator or copy constructor, no matter where the value came from, so no pilfering
was possible. In C++11, the answer is--you can!
上述讨论过程中最糟糕的是doubleValues()返回是一个临时值,一旦语句 v = doubleValues( v )执行完成,doubleValues(
v )的返回值就被销毁了。理论上,可以跳过拷贝,直接偷(plifer)到临时对象的指针(注:多像第一张解决方法),并保存到变量v。实际中,我们为什么不能move(移交,不是copy)对象呢??原因是:在C++03版中,无法区分一个对象是不是临时对象(因为C++03标准里面就没有右值引用这一概念嘛),因此也就无法偷(plifer)到临时对象的指针,只能不加区分的执行 copy
constructor(全盘拷贝,最简单)。

That's what rvalue references and move semantics are for! Move semantics allows you to avoid unnecessary copies when working with temporary objects that are about to evaporate, and
whose resources can safely be taken from that temporary object and used by another.
好了,上例的顽疾已经困扰程序员很久了,标准委员会终于开始重视这种当调用需要返回大量数据却只能全盘拷贝回来的效率问题了,因此,创造了右值引用这个概念,专门用来解决这类情况下避免全盘拷贝返回对象的数据的效率问题了

Move semantics relies on a new feature of C++11, called rvalue references, which you'll want to understand to really appreciate what's going on. So first let's talk about what an rvalue is, and then what an rvalue reference
is. Finally, we'll come back to move semantics and how it can be implemented with rvalue references.

Rvalues and lvalues - bitter rivals, or best of friends?

In C++, there are rvalues(右值) and lvalues(左值). An lvalue is an expression whose address can be taken, a locator value--essentially, an lvalue provides a (semi)permanent piece of memory. You can make assignments to lvalues.
For example:
左值具有一片永久或半永久的内存空间,具有固定的地址。

int a;
a = 1; // here, a is an lvalue


You can also have lvalues that aren't variables:

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

getRef() = 4;


Here, getRef returns a reference to a global variable, so it's returning a value that is stored in a permanent location. (You could literally write & getRef() if you wanted to, and it would give you the address of x.)
Rvalues are--well, rvalues are not lvalues. An expression is an rvalue if it results in a temporary object. For example:

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


Here, getVal() is an rvalue--the value being returned is not a reference to x, it's just a temporary value. This gets a little bit more interesting if we use real objects instead of numbers:

string getName ()
{
return "Alex";
}
getName();


Here, getName returns a string that is constructed inside the function. You can assign the result of getName to a variable:

string name = getName();


But you're assigning from a temporary object, not from some value that has a fixed location. getName() is an rvalue.
右值没有固定的地址,一旦赋值完成,右值随即被清除。

Detecting temporary objects with rvalue references(用rvalue reference指明是函数返回的对象)

The important thing is that rvalues refer to temporary objects--just like the value returned from doubleValues. Wouldn't it be great if we could know,without a shadow of a doubt, that a value returned from
an expression was temporary, and somehow write code that is overloaded to behave differently for temporary objects? Why, yes, yes indeed it would be. And this is what rvalue references are for. An rvalue reference is a reference that will bind only to a temporary
object. What do I mean?
问题的关键在于rvalue 指的是临时对象,就如doubleValues()返回的值一样,如果我们有一种方法能够知道 某个value 是temporary value,然后我们为它重载特殊的处理代码,这是不是很 perfect。 rvalue references
派上用场了。
Prior to C++11, if you had a temporary object, you could use a "regular" or "lvalue reference" to bind it, but only if it was const:
C++11标准之前,若你要引用临时对象,只能用“regular”做法,或把它当做本地对象一样来引用,而且必须是const引用(原因看下文补充)。

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


The intuition here is that you cannot use a "mutable" reference because, if you did, you'd be able to modify some object that is about to disappear, and thatwould be dangerous. Notice,
by the way, that holding on to a const reference to a temporary object ensures that the temporary object isn't immediately destructed. This is a nice guarantee of C++, but it is still a temporary object, so you don't want to modify
it(看补充).
可是返回对象不是存储在本地空间啊,它随时会被覆盖掉(它要是class类话,返回时自己就把自己析构了),所以我们最好不要尝试通过引用去修改它,这是非常危险的。

补充:在《C++ primer 第四版中文版》的Section 2.5中,关于const关键字有这样一段描述:const引用是指向const的引用,通俗讲就是const引用的对象也必须是const类型,如果不是const类型,例如下面的代码:

double dval = 3.14;
const int &ri = dval;



编译器会把上述代码转换成以下形式的代码:

double dval = 3.14;
double temp = dval;
const int &ri = dval;
ri 指向的值虽然是3.14,但temp和dval显然是两个不同地址的变量。temp实质还是保留拷贝后的副本,这同时解释了没什么const引用可以确保函数的返回值不会立刻消失。

/**************************************************************** 补充完毕 ***************************************************************************************************/

In C++11, however, there's a new kind of reference, an "rvalue reference", that will let youbind a mutable reference to an rvalue, but not an lvalue.
In other words,rvalue references are perfect for detecting if a value is temporary object or not. Rvalue referencesuse the && syntax instead of just &, andcan
be const and non-const, just like lvalue references, although you'll rarely see a const rvalue reference (as we'll see, mutable references are kind of the point):
C++11标准中就好了,可以用rvalue reference显式表明我就是引用了一个返回对象,这告诉编译器,你编译的时候,不要把它当lvalue reference来对待了,我为这种特殊情况指明了特定的解决方法,你应该去调用这种方法,在这种方法中,我已经确保了对该对象的修改不会影响其他程序,所以你不用限定我是const的了。

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


So far this is all well and good, but how does it help?The most important thing about lvalue references vs rvalue references is what happens when you write functions that take lvalue or rvalue
references as arguments. Let's say we have two functions:
定义了rvalue reference这么一个概念有什么用?当把它当参数传入函数内时,就非常有用。

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

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


Now the behavior gets interesting--the printReference function takinga const lvalue reference will accept any argument that it's given,whether it be an
lvalue or an rvalue, and regardless of whether the lvalue or rvalue is mutable or not. However, in the presence of the second overload, printReference takingan rvalue reference, it
will be given all values except mutable rvalue-references. In other words, if you write:
第一个printReference() 可以接受对左值类型和右值类型,第二个只能接受右值类型,说白了就是第一个printReference()把输入变量都当左值类型对待(C++已经确保返回值 const引用不会马上消失)

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


Now we have a way to determine if a reference variable refers to a temporary object or to a permanent object. The rvalue reference version of the method is like the secret back door entrance to the club that you can
only get into if you're a temporary object (boring club, I guess). Now that we have our method of determining if an object was a temporary or a permanent thing, how can we use it?
既然标准里面新定义了 rvalue reference ,而且编译器也不再限定你非得是const了,那你自己就要对会引发的后果负责了,那我们怎么来确保 rvalue
reference 的安全呢,并且解决开始例子中遇的问题???
答案就是::为类添加特殊构造函数(move constructor ) ,重载赋值操作(move assignment operator)(就是 = 号啦)

Move constructor and move assignment operator

The most common pattern you'll see when working with rvalue references is to create a move constructor and move assignment operator (which follows the same principles). A move constructor, like a copy constructor, takes
an instance of an object as its argument and creates a new instance based on the original object. However, the move constructor can avoid memory reallocation because we know it has been provided a temporary object, so rather than copy the fields of the object,
we will move them.
What does it mean to move a field of the object? If the field is a primitive type, like int, we just copy it. It gets more interesting if the field is a pointer:
here, rather than allocate and initialize new memory, we can simply steal the pointer and null out the pointer in the temporary object! We know the temporary object will no longer be needed, so we can take its pointer out from under it.
若临时变量只是最基本的int, double, float 等,就直接复制吧,花不了多少时间,若临时变量存储的是一大片数据,比如说巨大的数组,那改用rvalue reference 方法吧

最普遍的做法就是重载构造函数和赋值操作,直接将新对象的数据区域指向临时变量的数据区域,这样就不用重新分配空间和拷贝数据了,然后将临时对象中存储数据区域地址的指针成员变为NULL,临时对象则再也无法访问本来属于它的数据了,它析构时也就不可能释放掉数据区域(卧槽,新对象简直就是偷了临时对象的数据,而且还把临时对象的记忆给删除了)。看下面会更形象点。

Imagine that we have a simple ArrayWrapper class, like this:

classArrayWrapper
{
public:
ArrayWrapper (intn)
: _p_vals( newint[ n ] )
, _size( n )
{}
// copy constructor
ArrayWrapper (constArrayWrapper& other)
: _p_vals( newint[ other._size  ] )
, _size( other._size )
{
for(inti = 0; i < _size; ++i )
{
_p_vals[ i ] = other._p_vals[ i ];
}
}
~ArrayWrapper ()
{
delete[] _p_vals;
}
private:
int*_p_vals;
int_size;
};


Notice that the copy constructor has to both allocate memory and copy every value from the array, one at a time! That's a lot of work for a copy. Let's add a move constructor and gain some massive efficiency.

classArrayWrapper
{
public:
// default constructor produces a moderately sized array
ArrayWrapper ()
: _p_vals( newint[ 64 ] )
, _size( 64 )
{}

ArrayWrapper (intn)
: _p_vals( newint[ n ] )
, _size( n )
{}

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

// copy constructor
ArrayWrapper (constArrayWrapper& other)
: _p_vals( newint[ other._size  ] )
, _size( other._size )
{
for(inti = 0; i < _size; ++i )
{
_p_vals[ i ] = other._p_vals[ i ];
}
}
~ArrayWrapper ()
{
delete[] _p_vals;
}

private:
int*_p_vals;
int_size;
};


Wow, the move constructor is actually simpler than the copy constructor! That's quite a feat. The main things to notice are:

The parameter is a non-const rvalue reference
other._p_vals is set to NULL(还删除了临时对象的记忆)

The second observation explains the first--we couldn't set other._p_vals to NULL if we'd taken a const rvalue reference. But why do we need to set other._p_vals = NULL? The reason is the destructor--when the temporary
object goes out of scope, just like all other C++ objects, its destructor will run. When its destructor runs, it will free _p_vals. The same _p_vals that we just copied! If we don't set other._p_vals to NULL, the move would not really be a move--it would just
be a copy that introduces a crash later on once we start using freed memory. This is the whole point of a move constructor: to avoid a copy by changing the original, temporary object!
Again, the overload rules work such that the move constructor is called only for a temporary object--and only a temporary object that can be modified. One thing this means is that if you have a function that returns
a const object, it will cause the copy constructor to run instead of the move constructor--so don't write code like this:
只有当临时对象是可修改时,才会调用 move constructor 型构造函数,不然还是会全盘复制(调用 copy constructor),很简单嘛,若是临时对象不能修改,那新对象怎么去删除临时对象的记忆,临时对象析构的时候不就把数据区域一并删除了。所以不要像下面这样定义 返回类型是const类型的函数了。

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


There's still one more situation we haven't discussed how to handle in a move constructor--when we have a field that is an object. For example, imagine that instead of having a size field, we had a metadata field that
looked like this:
类中类

上面没有考虑类中类的情况,如下:

classMetaData
{
public:
MetaData (intsize, conststd::string& name)
: _name( name )
, _size( size )
{}

// copy constructor
MetaData (constMetaData& other)
: _name( other._name )
, _size( other._size )
{}

// move constructor
MetaData (MetaData&& other)
: _name( other._name )
, _size( other._size )
{}

std::string getName () const{ return_name; }
intgetSize () const{ return_size; }
private:
std::string _name;
int_size;
};


Now our array can have a name and a size, so we might have to change the definition of ArrayWrapper like so:

classArrayWrapper
{
public:
// default constructor produces a moderately sized array
ArrayWrapper ()
: _p_vals( newint[ 64 ] )
, _metadata( 64, "ArrayWrapper")
{}

ArrayWrapper (intn)
: _p_vals( newint[ n ] )
, _metadata( n, "ArrayWrapper")
{}

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

// copy constructor
ArrayWrapper (constArrayWrapper& other)
: _p_vals( newint[ other._metadata.getSize() ] )
, _metadata( other._metadata )
{
for( inti = 0; i < _metadata.getSize(); ++i )
{
_p_vals[ i ] = other._p_vals[ i ];
}
}
~ArrayWrapper ()
{
delete[] _p_vals;
}
private:
int*_p_vals;
MetaData _metadata;
};


Does this work? It seems very natural, doesn't it, to just call theMetaData move constructor from within the move constructor for ArrayWrapper? The problem is that this just doesn't
work. The reason is simple: the value of other in the move constructor--it's an rvalue reference. But an rvalue reference is not, in fact, an rvalue. It's an lvalue, (rvalue reference引用的是右值类型,但rvalue reference 的值本身是可修改的左值)and so the copy constructor is called,
not the move constructor. This is weird. I know--it's confusing. Here's the way to think about it. A rvalue is an expression that creates an object that is about to evaporate into thin air. It's on its last legs in life--or about to fulfill its life purpose.
Suddenly we pass the temporary to a move constructor, and it takes on new life in the new scope. In the context where the rvalue expression was evaluated, the temporary object really is over and done with. But in our constructor, the object has a name; it
will be alive for the entire duration of our function. In other words, we might use the variable other more than once in the function, and the temporary object has a defined location that truly persists for the entire function.It's
an lvalue in the true sense of the term locator value, we can locate the object at a particular address that is stable for the entire duration of the function call. We might, in fact, want to use it later in the function. If a move constructor were
called whenever we held an object in an rvalue reference, we might use a moved object, by accident!
上面的类成员MetaData是无法实现调用move constructor的,原因是rvalue
reference类型的值是左值类型,不是右值类型,因此是调用copy constructor,简单理解下:右值是什么??右值是快要消亡的对象,一旦它完成赋值,它随即消失,但是突然,我们把它传入move
constructor,它在move constructor的作用域内获得了新生,并在整个move constructor执行期间一直保持存活,这不跟左值的性质一样吗??所以它是左值,既然是左值类型,当然调用的是copy
constructor了
我认为还可以从另外一个方面来理解:上例中的other确实是rvalue
reference类型,但是other.MetaData对于临时对象来说却是实实在在的右值类型,因为我们可以定位到它的地址,所以会调用copy constructor。

// move constructor
ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals  )
, _metadata( other._metadata )
{
// if _metadata( other._metadata ) calls the move constructor, using
// other._metadata here would be extremely dangerous!
//如果 _metadata( other._metadata ) 也是调用 move constructor类型构造函数,在这
//操作other._metadata 是很危险的,因为如果_metadata( other._metadata )调用完move constructor类型构造函数后
//  other._metadata 就对自身的数据失去了记忆(想象以下若metadata也像ArrayWrapper类一样存储的是一大片数组
// 再去访问它的数据,那什么也得不到了
other._p_vals = NULL;
}


Put a final way: both lvalue and rvalue references are lvalue expressions. The difference is that an lvalue reference must be const to hold a reference to an rvalue, whereas an rvalue reference can always hold a reference
to an rvalue. It's like the difference between a pointer, and what is pointed to. The thing pointed-to came from an rvalue, but when we use rvalue reference itself, it results in an lvalue.
做个总结吧: lvalue and rvalue references 都是左值类型的值,不同的是 若lvalue reference 要指向一个 rvalue
,那lvalue reference必须是const,但rvalue reference却不用必须的const, rvalue references 就好比是指针和指针指向的内容,指针指向的内容是rvalue,但指针自身是lvalue类型。

std::move

So what's the trick to handling this case? We need to use std::move, from <utility>--std::move is a way of saying, "ok, honest to God I know I have an lvalue, but I want it to be an rvalue." std::move does not, in and
of itself, move anything; it just turns an lvalue into an rvalue, so that you can invoke the move constructor. Our code should look like this:
那对上面的情况是不是束手无策了呢???肯定不是,对,other.MetaData确实是lvalue,但只要我把它转换成 rvalue类型,不就会调用MetaData类的move
constructor吗,谢天谢地,std::move()l可以帮我们实现lvalue类型到rvalue类型的转换。它什么都不做,就是数据类型转换。

#include <utility> // for std::move

// move constructor
ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals  )
, _metadata( std::move( other._metadata ) )//强制转换成rvalue reference
{
other._p_vals = NULL;
}


And of course we should really go back to MetaData and fix its own move constructor so that it uses std::move on the string it holds:
string 也是类,所以别忘了在MetaData的move constructor 里做相应修改

MetaData (MetaData&& other)
: _name( std::move( other._name ) ) // oh, blissful efficiency
: _size( other._size )
{   

}


Move assignment operator
Just as we have a move constructor, we should also have a move assignment operator. You can easily write one using the same techniques as for creating a move constructor.

Move constructors and implicitly generated constructors

As you know, in C++ when you declare any constructor, the compiler will no longer generate the default constructor for you. The same is true here: adding a move constructor to a class will require you to declare and
define your own default constructor. On the other hand, declaring a move constructor does not prevent the compiler from providing an implicitly generated copy constructor, and declaring a move assignment operator does not inhibit the creation of a standard
assignment operator.
我们知道,一旦我们为类定义了一个构造函数,C++编译器就不会为我们添加默认的构造函数了,因此,如果我们还需要默认构造函数的话,我们需要显式添加,但是,添加重载赋值操作符(=)为调用move constructor的函数时,仍会保留保留默认调用copy constructor的“=”函数。

How does std::move work

You might be wondering, how does one write a function like std::move? How do you get this magical property of transforming an lvalue into an rvalue reference? The answer, as you might guess, is typecasting.
The actual declaration for std::move is somewhat more involved, but at its heart, it's just a static_cast to
an rvalue reference. This means, actually, that you don't really need to use move--but you should, since it's much more clear what you mean. The fact that a cast is required is, by the way, a very good thing! It means that you cannot accidentally
convert an lvalue into an rvalue, which would be dangerous since it might allow an accidental move to take place. You must explicitly use std::move (or a cast) to convert an lvalue into an rvalue reference, and an rvalue reference will never bind to an lvalue
on its own.
你一定好奇 我们自己 编写的函数怎么样才能实现 std::move 一样的功能。这样你就可以利用它的魔力了。答案你可能已经猜到了,就是typecasting。std::move的核心就是一个 static_cast ,这意味着你可以在自己的函数中实现std::move的功能而不调用std::move,但推荐你还是用std::move,这样程序更加清晰明了,别人一眼看过去就知道你std::move是要干嘛,所以你还是显式调用std::move来完成lvalue到rvalue
reference的转换吧
std::move核心代码:

template<typename T>
decltype(auto) move(T&& param)
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}


Returning an explicit rvalue-reference from a function

Are there ever times where you should write a function that returns an rvalue reference? What does it mean to return an rvalue reference anyway? Aren't functions that return objects by value already rvalues?
函数返回值一定是rvalue类型吗??不一定,看下面的例子
Let's answer the second question first: returning an explicit rvalue reference is different than returning an object by value. Take the following simple example:

int x;

int getInt ()
{
//返回 x 的拷贝,并不是x本身
return x;
}

int&& getRvalueInt ()
{
// notice that it's fine to move a primitive type--remember, std::move is just a cast
//返回 x 本身
returnstd::move( x );
}


Clearly in the first case, despite the fact that getInt() is an rvalue, there is a copy of the variable x being made. We can even see this by writing a little helper function:
查看上面函数返回值的地址,更能明白它俩不是一回事

voidprintAddress (constint& v) // const ref to allow binding to rvalues
{
cout << reinterpret_cast<constvoid*>( & v ) << endl;
}

printAddress( getInt() );
printAddress( x );


When you run this program, you'll see that there are two separate values printed.
On the other hand,

printAddress( getRvalueInt() );
printAddress( x );


prints the same value because we are explicitly returning an rvalue here.
So returning an rvalue reference is a different thing than not returning an rvalue reference, but this difference manifests itself most noticeably if you have a pre-existing object you are returning instead of a temporary
object created in the function (where the compiler is likely to eliminate the copy for you).
所以返回 rvalue reference 的主要特性是:如果你有一个已经存在的 object,而在调用函数中,你又显式的返回这个object 的 rvalue reference ,那么返回的 rvalue reference 指向的是已经存在的 object(反之,编译器默认情况下很可能返回的是已经存在的object 的副本)。
Now on to the question of whether you want to do this. The answer is: probably not. In most cases, it just makes it more likely that you'll end up with a dangling reference (a case where the reference exists, but the
temporary object that it refers to has been destroyed). The issue is quite similar to the danger of returning an lvalue reference--the referred-to object may no longer exist.Rvalue references cannot magically keep an object alive
for you. Returning an rvalue reference would primarily make sense in very rare cases where you have a member function and need to return the result of calling std::move on a field of the class from that function--and how often are you going to do that?
现在看这个问题:你是否希望这样返回rvalue reference?答案可能是:不希望。大多数情况下,你最后很可能会得到一个野指针(指针指向的内容已经被destroyed了),这跟返回 lvalue reference 一样,指向的内容已经不存在了。保存rvalue reference 并不会自动的使rvalue
reference 指向的对象保持alive,而不被destroy。返回 rvalue reference 主要运用于比较少特殊的情况:你有一个函数,通过调用这个函数,同时通过调用std::move()来返回一片区域的数据。问题是你会经常这样做吗??(这样做不优雅嘛)

Move semantics and the standard library

Going back to our original example--we were using a vector, and we don't have control over the vector class and whether or not it has a move constructor or move assignment operator. Fortunately, the standards committee
is wise, and move semantics has been added to the standard library. This means that you can now efficiently return vectors, maps, strings and whatever otherstandard library objects you want, taking full advantage of move
semantics.
回到最初的例子:我们用到了std::vector,然而我们并不知道vector类是不是定义了move constructor(move类型构造函数)或着重载了“=”操作符,而且我们还不能修改vector源代码或者其他一些控制之类的。幸运的是,标准化委员会很机智,他们已经把move语法加进C++11标准库了,这意味着,只要你喜欢,你可以高效的返回vectors,maps,strings以及其他标准库对象objects,尽情享用move语法的优势吧!!

Moveable objects in STL containers

In fact, the standard library goes one step further. If you enable move semantics in your own objects by creating move assignment operators and move constructors, when you store those objects in a container, the STL will
automatically use std::move, automatically taking advantage of move-enabled classes to eliminate inefficient copies.
实际上,标准化委员会想的更远。如果你在你自己定义的类中实现了move()函数和move constructors(move类型构造函数),当你把它的实例对象放入STL容器(例如std::vector)中,STL会自动的调用std::move完成高效的 数据引用,而不是低效率的深度拷贝。

Move semantics and rvalue reference compiler support

Rvalue references are supported by GCC, the Intel compiler and MSVC.
Previous: Generalized Constant Expressions in C++11 Learn
how C++11 makes compile-time processing easier than ever

Next: Nullptr and Strongly Typed Enums (Enum classes)
in C++11 Learn how C++11 improves type safety

VS2013部分支持C++11,VS2015完全支持C++11,VS2010不明。

转载请注明作者和出处:http://blog.csdn.net/holamirai,未经允许请勿用于商业用途。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: