在c++11标准中,一个显著的特点是引入了move semantic 和 perfect forwarding,这个新特性并非简单的“语法糖”,使用得当,可以带来程序性能的显著增强。
理解这两个概念,就必须要涉及到lvalue和rvalue,那么,我们就从lvalue和rvalue说起。在c语言的历史上,lvalue是left value的缩写,因为它可能出现在赋值表达式的左边,rvalue同理。c++继承了这样一个历史表达习惯,因此也引入了lvalue和rvalue,但是随着c++语言的发展,“left”和“right”已经变得不再准确,其中一个原因便是,我们在很多不是赋值表达式的地方也开始大量使用这两个概念,因此我们现在只认为l和r是两个不带字面意义的前缀。
实际上,WG21(ISO/IEC JTC1/SC22/WG21)的Core subgroup主席William M. Miller,甚至在2010年提交了一篇专门关于名为《A Taxonomy of Expression Value Categories》[1]的提议,他想把表达式的值这样分类:
在这篇20页的论文中,他反复谈的就是这个问题,如果有兴趣可以去看这篇文章[1]。但是估计这个分类把人搞得很晕,所以这个论文似乎没有流行起来。
那么,如果我们不能通过左右来区分lvalue和rvalue,又应该怎样识别它们呢?其实可以问这样一个问题,“能否取得这个值的地址”,如果能,那么就是一个lvalue,反之就是一个rvalue。比如 &obj , &*ptr , &ptr[index] , and &++x 都是合法的,而 &1729 , &(x + y) , &std::string("meow") , and &x++ 都是非法的。微软VC++ 开发人员Stephan T. Lavavej 写了一篇很好的、很深刻的也很长的关于这个主题的文章《Rvalue References: C++0x Features in VC10》,如果要更详细的了解大量细节,可以精读此文[2]。
在简要介绍了lvalue和rvalue之后,就该进入正题了。在Howard E. Hinnant等提交的《A Proposal to Add an Rvalue Reference to the C++ Language》[3]中,他指出在C++中加入这个新特性有三个目的:
(1)消除针对用户定义类型的copy带来的巨大开销(move);
(2)解决通用的转发工具的易用性问题(forward);
(3)一个N1377提议中的错误(可忽略)。
针对上面两个(不包括3)如何被解决,Howard E. Hinnant等写了一篇名为《A Brief Introduction to Rvalue References》的文章[4],合作作者甚至包括了Bjarne Stroustrup。在该文章介绍了关于move 和 forwar的基本用法,和相比传统方法带来的优势。下面是关于move和forward在cplusplus.com上的例子:
// move example
#include <utility> // std::move
#include <iostream> // std::cout
#include <vector> // std::vector
#include <string> // std::string
int main () {
std::string foo = "foo-string";
std::string bar = "bar-string";
std::vector<std::string> myvector;
myvector.push_back (foo); // copies
myvector.push_back (std::move(bar)); // moves
std::cout << "myvector contains:";
for (std::string& x:myvector) std::cout << ' ' << x;
std::cout << '\n';
return 0;
}
Output:
myvector contains: foo-string bar-string
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a);
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
Output:
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]
参考文献:
[1] A Taxonomy of Expression Value Categories, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3055.pdf
[2] Rvalue References: C++0x Features in VC10, http://blogs.msdn.com/b/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx
[3] A Proposal to Add an Rvalue Reference to the C++ Language, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html
[4] A Brief Introduction to Rvalue References, http://www.artima.com/cppsource/rvalue.html