左值右值是表达式的属性,该属性称为 value category。按该属性分类,每一个表达式属于下列之一:
lvalue |
left value,传统意义上的左值 |
xvalue |
expiring value, x值,指通过“右值引用”产生的对象 |
prvalue |
pure rvalue,纯右值,传统意义上的右值(?) |
而 xvalue 和其他两个类型分别复合,构成:
lvalue + xvalue = glvalue |
general lvalue,泛左值 |
xvalue + prvalue = rvalue |
右值 |
1.区分
++x 与 x++ 假定x的定义为 int x=0;,那么前者是 lvalue,后者是rvalue。前者修改自身值,并返回自身;后者先创建一个临时对像,为其赋值,而后修改x的值,最后返回临时对像。区分表达式的左右值属性有一个简便方法:若可对表达式用 & 符取址,则为左值,否则为右值。比如
&obj , &*ptr , &ptr[index] , &++x |
有效 |
&1729 , &(x + y) , &std::string("meow"), &x++ |
无效 |
对于函数调用,根绝返回值类型不同,可以是lvalue、xvalue、prvalue:
-
The result of calling a function whose return type is an lvalue reference is an lvalue
-
The result of calling a function whose return type is an rvalue reference is an xvalue.
-
The result of calling a function whose return type is not a reference is a prvalue.
2.const vs non-const
左值和右值表达式都可以是const或non-const。比如,变量和函数的定义为:
1 string one("lvalue"); 2 const string two("clvalue"); 3 string three() { return "rvalue"; } 4 const string four() { return "crvalue"; }
那么表达式:
表达式 |
分类 |
one |
modifiable lvalue |
two |
const lvalue |
three() |
modifiable rvalue |
four() |
const rvalue |
引用
Type& |
只能绑定到可修改的左值表达式 |
const Type& |
可以绑定到任何表达式 |
Type&& |
可绑定到可修改的左值或右值表达式 |
const Type&& |
可以绑定到任何表达式 |
3.重载函数
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 string one("lvalue"); 6 const string two("clvalue"); 7 string three() { return "rvalue"; } 8 const string four() { return "crvalue"; } 9 10 void func(string& s) 11 { 12 cout << "func(string& s): " << s << endl; 13 } 14 15 void func(const string& s) 16 { 17 cout << "func(const string& s): " << s << endl; 18 } 19 20 void func(string&& s) 21 { 22 cout << "func(string&& s): " << s << endl; 23 } 24 25 void func(const string&& s) 26 { 27 cout << "func(const string&& s): " << s << endl; 28 } 29 30 int main() 31 { 32 func(one); 33 func(two); 34 func(three()); 35 func(four()); 36 return 0; 37 }
结果:
func(string& s): lvalue
func(const string& s): clvalue
func(string&& s): rvalue
func(const string&& s): crvalue
如果只保留const string& 和 string&& 两个重载函数,结果为:
func(const string& s): lvalue
func(const string& s): clvalue
func(string&& s): rvalue
func(const string& s): crvalue
4.右值引用
C++0x第5章的第6段:
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.
- 具名右值引用被视为左值
- 无名对对象的右值引用被视为x值
- 对函数的右值引用无论具名与否都将被视为左值
1 #include <iostream> 2 #include <string> 3 4 void F1(int&& a) 5 { 6 std::cout<<"F1(int&&) "<<a<<std::endl; 7 } 8 9 void F1(const int& a) 10 { 11 std::cout<<"F1(const int&) "<<a<<std::endl; 12 } 13 14 void F2(int&& a) 15 { 16 F1(a); 17 } 18 19 int main() 20 { 21 int && a=1; 22 F2(a); 23 F1(a); 24 F2(2); 25 F1(2); 26 return 0; 27 }
结果
F1(const int&) 1
F1(const int&) 1
F1(const int&) 2
F1(int&&) 2
5.移动语义
在这之前,如果写一个交换两个值的swap函数:
1 template <class T> swap(T& a, T& b) 2 { 3 T tmp(a); // now we have two copies of a 4 a = b; // now we have two copies of b 5 b = tmp; // now we have two copies of tmp 6 }
之后
1 template <class T> swap(T& a, T& b) 2 { 3 T tmp(std::move(a)); 4 a = std::move(b); 5 b = std::move(tmp); 6 }
std::move 接受左值或右值参数,并返回一个右值(其所作工作很简单)
1 template <class T> 2 typename remove_reference<T>::type&& 3 move(T&& a) 4 { 5 return a; 6 }
要是的swap真正发挥作用,需要重载:
1 class T 2 { 3 public: 4 T(T&& ); 5 T& operator = (T&& ); 6 ...
6.模板参数类型
为了对比左值引用和右值引用,一开始误打误撞,写了这样一个函数
1 template <typename Type> void Swap(Type&& sb1, Type&& sb2) 2 { 3 Type sb(sb1); 4 sb1 = sb2; 5 sb2 = sb; 6 }
然后
1 int main() 2 { 3 int a=1, b=2; 4 Swap(a, b); 5 std::cout<<a<<" "<<b<<std::endl; 6 return 0; 7 }
结果却是
2 2
不用整数,换用一个自定义的类型试试看:
1 class A 2 { 3 public: 4 A() { 5 std::cout << "Default constructor." << std::endl; 6 m_p = NULL; 7 } 8 9 ~A() { 10 std::cout << "Destructor." << std::endl; 11 delete m_p; 12 } 13 14 explicit A(const int n) { 15 std::cout << "Unary constructor." << std::endl; 16 m_p = new int(n); 17 } 18 19 A(const A& other) { 20 std::cout << "Copy constructor." << std::endl; 21 if (other.m_p) { 22 m_p = new int(*other.m_p); 23 } else { 24 m_p = NULL; 25 } 26 } 27 28 A(A&& other) { 29 std::cout << "Move constructor." << std::endl; 30 m_p = other.m_p; 31 other.m_p = NULL; 32 } 33 34 A& operator=(const A& other) { 35 std::cout << "Copy assignment operator." << std::endl; 36 if (this != &other) { 37 delete m_p; 38 if (other.m_p) { 39 m_p = new int(*other.m_p); 40 } else { 41 m_p = NULL; 42 } 43 } 44 return *this; 45 } 46 47 A& operator=(A&& other) { 48 std::cout << "Move assignment operator." << std::endl; 49 if (this != &other) { 50 delete m_p; 51 m_p = other.m_p; 52 other.m_p = NULL; 53 } 54 return *this; 55 } 56 57 int get() const { 58 return m_p ? *m_p : 0; 59 } 60 61 private: 62 int * m_p; 63 }; 64 65 int main() 66 { 67 A a(1); 68 A b(2); 69 Swap2(a, b); 70 std::cout<<a.get()<<" "<<b.get()<<std::endl; 71 return 0; 72 }
结果
Unary constructor.
Unary constructor.
Copy assignment operator.
Copy assignment operator.
2 2
Destructor.
Destructor.
只出现了两个对象,那么Swap中的临时对象去哪儿了?
C++0x 14.8.2.1
If 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.
1 template <class T> int f(T&&); 2 template <class T> int g(const T&&); 3 int i; 4 int n1 = f(i); // calls f<int&>(int&) 5 int n2 = f(0); // calls f<int>(int&&) 6 int n3 = g(i); // error: would call g<int>(const int&&), which 7 // would bind an rvalue reference to an lvalue
也就是前面提到的
1 template <typename Type> void Swap(Type&& sb1, Type&& sb2)
参数推导后
1 void Swap<int&>(int& sb1, int& sb1)