简介
Lvalue & Rvalue
- 左值可以运用&操作符取得地址,注意临时对象是无法取得地址的,因为很容易导致问题。
- 左值必然有一个名字
- 不是左值的是右值
int f(); int i; int& g(); int&& h(); &f; // f is lvalue, &f returns to pointer to the function f(); // f() is rvalue, as f returns a int by value i; // i is lvalue g(); // g() is lvalue, as f returns a lvalue reference to int h(); // h() is rvalue, as f returns a rvalue reference to int void f(int&& i) { // i is a rvalue reference, but i is a lvalue as named rvalue reference is lvalue }
这里需要注意的是函数的返回值,一个函数调用通常在返回值是左值引用的时候才是左值(见n3242 5.2.2/10,下面截取标准中的文字以供参考)。
5.2.2/10A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function
type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.
5/6In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue
references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether
named or not
Reference
左值引用形如T&,而右值引用形如T&&,并且我们知道右值引用可以绑定到右值,那么我们时候可以绑定到一个右值常量?因为常量是不可修改的,但是由于T&&不是reference to const,所以是否成立?
答案是可以的,请看如下例子:
#include <iostream> using namespace std; int main() { int&& rri = 5; rri = 4; cout << rri << endl; // error // char const*&& rrcc = "hello"; // *rrcc = '1'; }
这里在g++4.7.3中运行后可以发现rri的值是4。
这里涉及到reference的初始化,标准中规定,对于使用右值来初始化一个右值引用,可以创建一个拷贝,即,这里5会被保存在一个对象中,所以这里我们对这个对象进行修改。标准中8.5.3/5中如下描述(部分):
8.5.3/5
— Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const), or the reference shall be an rvalue reference.If the initializer exrepssion is xvalue or ...
— Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression
using the rules for a non-reference copy-initialization
再看下面注释中的代码,由于rrcc的类型是reference to pointer to const char,所以我们在后面对char const*赋值时出错。
Move Constructor
class foo { public: foo(foo&& f) : m_s(f.m_s) // m_s(move(f.m_s)) { } private: string m_s; };
这里,foo(foo&&)是move ctor,由于f是右值引用,我们认为,我们可以通过直接调用string的move ctor而不做任何处理。这是错误的。结果这里只有string的copy ctor被调用。
std::move
static_cast<T&&>(v)
当然具体实现会复杂一下,不过请继续接着看。
Perfect Forwarding
void f(foo const& a); void f(foo&& a);
但是当参数数量变多时怎么办,假设有N个参数,那么显然我们需要重载2^N个函数才能解决问题,所以引入了perfect forwarding。
function template
void g(int const&); void g(int&&); template<typename T> void f(T&& v) { g(forward<T>(v)); }
这里标准库函数forward完成了类型转发。抱枕传递给的g的类型的左右值属性是用户传入的属性。
14.8.2.1/3If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. If P is an rvalue reference to a cv-unqualified template parameter and
the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.
Reference Collapsing Rule
- T& && = T&
- T&& & = T&
- T& & = T&
- T&& && = T&&
这里的意思是,当一个类型,比如T& &&时,最终得到的类型是T&。可以看出,仅当T&& &&的情况,类型才是右值引用,其他情况都是左值引用。
Deduction
Template <class T> int f(T&&); template <class T> int g(const T&&); int i; int n1 = f(i); // calls f<int&>(int&) int n2 = f(0); // calls f<int>(int&&) int n3 = g(i); // error: would call g<int>(const int&&), which // would bind an rvalue reference to an lvalue
先来看f(i)的调用,根据14.8.2.1/3,我们有
- P = T&&,由于P是右值引用,所以Deduced A = T
- 由于P是右值引用,并且i是左值,并且i的类型是int,所以Transformed A = int&
- Deduced A = Transformed A => T = int&
所以我们将T = int&代入f,得到f<int&>(int& &&),根据reference collapsing rule,我们可以得出f的参数类型是int&,并且是左值,是我们要的。
- P = T&&,由于P是右值引用,所以Deduced A = T
- 0是右值,所以Transformed A = int
- Deduced A = Transformed A => T = int
所以最后得到f的签名为f<int>(int&&),参数类型是右值引用。
std::forward
template<typename T> T&& forward(std::remove_reference<T>::type& v) { return static_cast<T&&>(v); }
又是一个函数模板。假设我们指定T = int&,那么将会有:
- remove_reference<T>::type& = int&
- static_cast<int& &&>(v) = static_cast<int&>(v)
- 返回 int& && = int&
这里再一次用到了reference collapsing rule,我们将T指定为int&,返回值也是int&,perfect!!! forwarding。
- remove_reference<T>::type& = int&,这里还是左值引用,没错,还记得我们传递给forward的也是左值吗?虽然它是右值引用。
- static_cast<int&& &&>(v) = static_cast<int&&>(v)
- 返回int&& && = int&&
perfect!!!。
Explictly specifiec template parameter
template<typename T> T&& forward(T& v) { return static_cast<T&&>(v); }
我们还是可以推导出,这里也能够实现完美转发(有的读者可能认为无法对参数是const的对象进行转发,事实不是如此,我们尽在f(T&&)内部使用,所以不存在这个问题,可以试着自己推导)。
template<typename T> void f(T&& v) { g(forward(v)); }
假设我们传入一个左值int,那么v的类型就是int&,而传入forward的v的类型在进行推导前会被变成int(根据标准中5.5),所以我们推导出forward的T = int,
引用文档
- N3242,c++标准草案,有更新的版本,自行google。
- C++ Rvalue Reference Explained
- ACCU Overload 111 中的 Universal Reference