表示式左值右值(C++學習)
左值右值是表示式的屬性,該屬性稱為 value category。按該屬性分類,每一個表示式屬於下列之一:
lvalue |
left value,傳統意義上的左值 |
xvalue |
expiring value, x值,指通過“右值引用”產生的物件 |
prvalue |
pure rvalue,純右值,傳統意義上的右值(?) |
而 xvalue 和其他兩個型別分別複合,構成:
lvalue + xvalue = glvalue |
general lvalue,泛左值 |
xvalue + prvalue = rvalue |
右值 |
區分?
++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 anlvalue referenceis an lvalue
-
The result of calling a function whose return type is anrvalue referenceis an xvalue.
-
The result of calling a function whose return type isnot a referenceis a prvalue.
const vs non-const
左值和右值表示式都可以是const或non-const。
比如,變數和函式的定義為:
string one("lvalue"); const string two("clvalue"); string three() { return "rvalue"; } const string four() { return "crvalue"; }
那麼表示式:
表示式 |
分類 |
one |
modifiable lvalue |
two |
const lvalue |
three() |
modifiable rvalue |
four() |
const rvalue |
引用
Type& |
只能繫結到可修改的左值表示式 |
const Type& |
可以繫結到任何表示式 |
Type&& |
可繫結到可修改的左值或右值表示式 |
const Type&& |
可以繫結到任何表示式 |
過載函式
#include <iostream> #include <string> using namespace std; string one("lvalue"); const string two("clvalue"); string three() { return "rvalue"; } const string four() { return "crvalue"; } void func(string& s) { cout << "func(string& s): " << s << endl; } void func(const string& s) { cout << "func(const string& s): " << s << endl; } void func(string&& s) { cout << "func(string&& s): " << s << endl; } void func(const string&& s) { cout << "func(const string&& s): " << s << endl; } int main() { func(one); func(two); func(three()); func(four()); return 0; }
結果:
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
右值引用
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值
- 對函式的右值引用無論具名與否都將被視為左值
#include <iostream> #include <string> void F1(int&& a) { std::cout<<"F1(int&&) "<<a<<std::endl; } void F1(const int& a) { std::cout<<"F1(const int&) "<<a<<std::endl; } void F2(int&& a) { F1(a); } int main() { int && a=1; F2(a); F1(a); F2(2); F1(2); return 0; }
結果
F1(const int&) 1 F1(const int&) 1 F1(const int&) 2 F1(int&&) 2
移動語義
在這之前,如果寫一個交換兩個值的swap函式:
template <class T> swap(T& a, T& b) { T tmp(a); // now we have two copies of a a = b; // now we have two copies of b b = tmp; // now we have two copies of tmp (aka a) }
之後
template <class T> swap(T& a, T& b) { T tmp(std::move(a)); a = std::move(b); b = std::move(tmp); }
std::move 接受左值或右值引數,並返回一個右值(其所作工作很簡單)
template <class T> typename remove_reference<T>::type&& move(T&& a) { return a; }
要是的swap真正發揮作用,需要過載:
class T { public: T(T&& ); T& operator = (T&& ); ...
模板引數型別
為了對比左值引用和右值引用,一開始誤打誤撞,寫了這樣一個函式
template <typename Type> void Swap(Type&& sb1, Type&& sb2) { Type sb(sb1); sb1 = sb2; sb2 = sb; }
然後
int main() { int a=1, b=2; Swap(a, b); std::cout<<a<<" "<<b<<std::endl; return 0; }
結果卻是
2 2
不用整數,換用一個自定義的型別試試看:
class A { public: A() { std::cout << "Default constructor." << std::endl; m_p = NULL; } ~A() { std::cout << "Destructor." << std::endl; delete m_p; } explicit A(const int n) { std::cout << "Unary constructor." << std::endl; m_p = new int(n); } A(const A& other) { std::cout << "Copy constructor." << std::endl; if (other.m_p) { m_p = new int(*other.m_p); } else { m_p = NULL; } } A(A&& other) { std::cout << "Move constructor." << std::endl; m_p = other.m_p; other.m_p = NULL; } A& operator=(const A& other) { std::cout << "Copy assignment operator." << std::endl; if (this != &other) { delete m_p; if (other.m_p) { m_p = new int(*other.m_p); } else { m_p = NULL; } } return *this; } A& operator=(A&& other) { std::cout << "Move assignment operator." << std::endl; if (this != &other) { delete m_p; m_p = other.m_p; other.m_p = NULL; } return *this; } int get() const { return m_p ? *m_p : 0; } private: int * m_p; }; int main() { A a(1); A b(2); Swap2(a, b); std::cout<<a.get()<<" "<<b.get()<<std::endl; return 0; }
結果
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. 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
也就是前面提到的
template <typename Type> void Swap(Type&& sb1, Type&& sb2)
引數推導後
void Swap<int&>(int& sb1, int& sb1)