C++/C++11中左值、左值引用、右值、右值引用的使用
? ? ? C++的表達式要不然是右值(rvalue),要不然就是左值(lvalue)。這兩個名詞是從C語言繼承過來的,原本是為了幫助記憶:左值可以位于賦值語句的左側,右值則不能。
? ? ? 在C++語言中,二者的區別就沒那么簡單了。一個左值表達式的求值結果是一個對象或者一個函數,然而以常量對象為代表的某些左值實際上不能作為賦值語句的左側運算對象。此外,雖然某些表達式的求值結果是對象,但它們是右值而非左值。可以做一個簡單的歸納:當一個對象被用作右值的時候,用的是對象的值(內容);當對象被用作左值的時候,用的是對象的身份(在內存中的位置)。左值與右值的根本區別在于是否允許取地址&運算符獲得對應的內存地址。
? ? ? 不同的運算符對運算對象的要求各不相同,有的需要左值運算對象,有的需要右值運算對象;返回值也有差異,有的得到左值結果,有的得到右值結果。一個重要的原則(有一種例外的情況),是在需要右值的地方可以用左值來代替,但是不能把右值當成左值(也就是位置)使用。當一個左值被當成右值使用時,實際使用的是它的內容(值)。
? ? ? 運算符用到左值的包括:(1).賦值運算符需要一個(非常量)左值作為其左側運算對象,得到的結果也仍然是一個左值。(2).取地址符作用于一個左值運算對象,返回一個指向該運算對象的指針,這個指針是一個右值。(3). 內置解引用運算符、下標運算符、迭代器解引用運算符、string和vector的下標運算符的求值結果都是左值。(4). 內置類型和迭代器的遞增遞減運算符作用于左值運算對象,其前置版本所得的結果也是左值。
? ? ? 使用關鍵字decltype的時候,左值和右值也有所不同。如果表達式的求值結果是左值,decltype作用于該表達式(不是變量)得到一個引用類型。舉個例子,假定p的類型是int*,因為解引用運算符生成左值,所以decltype(*p)的結果是int&。另一方面,因為取地址運算符生成右值,所以decltype(&p)的結果是int**,也就是說,結果是一個指向整型指針的指針。
? ? ? 賦值運算符的左側運算對象必須是一個可修改的左值。賦值運算符的結果是它的左側運算對象,并且是一個左值。相應的,結果的類型就是左側運算對象的類型。如果賦值運算符的左右兩個運算對象類型不同,則右側運算對象將轉換成左側運算對象的類型。
? ? ? 遞增和遞減運算符有兩種形式:前置版本和后置版本。這兩種運算符必須作用于左值運算對象。前置版本將對象本身作為左值返回,后置版本則將對象原始值的副本作為右值返回。
? ? ? 箭頭運算符作用于一個指針類型的運算對象,結果是一個左值。點運算符分成兩種情況:如果成員所屬的對象是左值,那么結果是左值;反之,如果成員所屬的對象是右值,那么結果是右值。
? ? ? 當條件運算符的兩個表達式都是左值或者能轉換成同一種左值類型時,運算的結果是左值;否則運算的結果是右值。
? ? ? 對于逗號運算符來說,首先對左側的表達式求值,然后將求值結果丟棄掉。逗號運算符真正的結果是右側表達式的值。如果右側運算對象是左值,那么最終的求值結果也是左值。
? ? ? 左值(lvalue):是指那些求值結果為對象或函數的表達式。一個表示對象的非常量左值可以作為賦值運算符的左側運算對象。
? ? ? 右值(rvalue):是指一種表達式,其結果是值而非值所在的位置。
? ? ? 函數返回類型:函數的返回類型決定函數調用是否是左值。調用一個返回引用的函數得到左值,其它返回類型得到右值。可以像使用其它左值那樣來使用返回引用的函數的調用,特別是,我們能為返回類型是非常量引用的函數的結果賦值。把函數調用放在賦值語句的左側可能看起來有點奇怪,但其實這沒什么特別的。返回值是引用,因此調用是個左值,和其它左值一樣它也能出現在賦值運算符的左側。如果返回類型是常量引用,我們不能給調用的結果賦值,這一點和我們熟悉的情況是一樣的。
? ? ? 嚴格來說,當我們使用術語”引用(reference)”時,指的其實是”左值引用(lvalue reference)”。C++11中新增了一種引用,右值引用(rvalue reference),這種引用主要用于內置類。
? ? ? 為了支持移動操作,C++11引入了一種新的引用類型----右值引用(rvalue reference)。所謂右值引用就是必須綁定到右值的引用。通過&&而不是&來獲得右值引用。右值引用有一個重要的性質----只能綁定到一個將要銷毀的對象。因此,可以自由地將一個右值引用的資源”移動”到另一個對象中。
? ? ? 左值和右值是表達式的屬性。一些表達式生成或要求左值,而另外一些則生成或要求右值。一般而言,一個左值表達式表示的是一個對象的身份,而一個右值表達式表示的是對象的值。
? ? ? 類似任何引用,一個右值引用也不過是某個對象的另一個名字而已。對于常規引用(為了與右值引用區分開來,可以稱之為左值引用(lvalue reference)),不能將其綁定到要求轉換的表達式、字面常量或是返回右值的表達式。右值引用有著完全相反的綁定特性:可以將一個右值引用綁定到這類表達式上,但不能將一個右值引用直接綁定到一個左值上。
? ? ? 返回左值引用的函數,連同賦值、下標、解引用和前置遞增/遞減運算符,都是返回左值的表達式的例子。可以將一個左值引用綁定到這類表達式的結果上。
? ? ? 返回非引用類型的函數,連同算術、關系、位以及后置遞增/遞減運算符,都生成右值。不能將一個左值引用綁定到這類表達式上,但可以將一個const的左值引用或者一個右值引用綁定到這類表達式上。
? ? ? 左值持久,右值短暫:左值有持久的狀態,而右值要么是字面常量,要么是在表達式求值過程中創建的臨時對象。
? ? ? 由于右值引用只能綁定到臨時對象,可知:所引用的對象將要被銷毀;該對象沒有其它用戶。這兩個特性意味著,使用右值引用的代碼可以自由地接管所引用的對象的資源。右值引用指向將要銷毀的對象。因此,我們可以從綁定到右值引用的對象”竊取”狀態。
? ? ? 變量是左值:變量可以看作只有一個運算對象而沒有運算符的表達式,雖然我們很少這樣看待變量。類似其它任何表達式,變量表達式也有左值/右值屬性。變量表達式都是左值。帶來的結果就是,我們不能將一個右值引用綁定到一個右值引用類型的變量上。其實有了右值表示臨時對象這一觀察結果,變量是左值這一特性并不令人驚訝。畢竟,變量是持久的,直至離開作用域時才被銷毀。變量是左值,因此我們不能將一個右值引用直接綁定到一個變量上,即使這個變量是右值引用類型也不行。
? ? ? 標準庫std::move函數:雖然不能將一個右值引用直接綁定到一個左值上,但可以顯示地將一個左值轉換為對應的右值引用類型。我們還可以通過調用一個名為move的新標準庫函數來獲得綁定到左值上的右值引用,此函數定義在頭文件utility中。move調用告訴編譯器:有一個左值,但希望像一個右值一樣處理它。我們必須認識到,在調用move之后,我們不能對移后源對象的值做任何假設。我們可以銷毀一個移后源對象,也可以賦予它新值,但不能使用一個移后源對象的值。
? ? ? 移動構造函數和移動賦值運算符接受一個(通常是非const的)右值引用;而拷貝版本則接受一個(通常是const的)普通左值引用。
? ? ? 左值引用(lvalue reference):可以綁定到左值的引用。右值引用(rvalue reference):指向一個將要銷毀的對象的引用。
? ? ? 引用限定符(referencequalifier):用來指出一個非static成員函數可以用于左值或右值的符號。限定符&和&&應該放在參數列表之后或const限定符之后(如果有的話)。被&限定的函數只能用于左值;被&&限定的函數只能用于右值。
? ? ? 如果一個函數參數是指向模板參數類型的右值引用(如,T&&),則可以傳遞給它任意類型的實參。如果將一個左值傳遞給這樣的參數,則函數參數被實例化為一個普通的左值引用(T&)。
? ? ? 右值引用是C++11中最重要的新特性之一,它解決了C++中大量的歷史遺留問題,使C++標準庫的實現在多種場景下消除了不必要的額外開銷(如std::vector, std::string),也使得另外一些標準庫(如std::unique_ptr,std::function)成為可能。即使你并不直接使用右值引用,也可以通過標準庫,間接從這一新特性中受益。
? ? ? 右值引用的意義通常解釋為兩大作用:移動語義(Move Sementics)和完美轉發(Perfect Forwarding)。它的主要目的有兩個方面:(1)、消除兩個對象交互時不必要的對象拷貝,節省運算存儲資源,提高效率;(2)、能夠更簡潔明確地定義泛型函數。
? ? ? 右值引用可以使我們區分表達式的左值和右值。
? ? ? 右值引用主要就是解決一個拷貝效率低下的問題,因為針對于右值,或者打算更改的左值,我們可以采用類似與unique_ptr的move(移動)操作,大大的提高性能(move semantics)。另外,C++的模板推斷機制為參數T&&做了一個例外規則,讓左值和右值的識別和轉向(forward)非常簡單,幫助我們寫出高效并且簡捷的泛型代碼(perfect forwarding)。
? ? ? 下面是從其他文章中copy的測試代碼,詳細內容介紹可以參考對應的reference:
#include "lvalue_rvalue.hpp"
#include <iostream>
#include <string>
#include <utility>namespace lvalue_rvalue_ {namespace {
char& get_val(std::string& str, std::string::size_type ix)
{return str[ix];
}
}int test_lvalue_rvalue_1()
{
{// 賦值運算符的左側運算對象必須是一個可修改的左值:int i = 0, j = 0, k = 0; // 初始化而非賦值const int ci = i; // 初始化而非賦值// 下面的賦值語句都是非法的//1024 = k; // 錯誤:字面值是右值//i + j = k; // 錯誤:算術表達式是右值//ci = k; // 錯誤:ci是常量(不可修改的)左值
}{int i = 0;int& r = i; // 正確:r引用i//int&& rr = i; // 錯誤:不能將一個右值引用綁定到一個左值上//int& r2 = i * 42; // 錯誤:i*42是一個右值const int& r3 = i * 42; // 正確:我們可以將一個const的引用綁定到一個右值上int&& rr2 = i * 42; // 正確:將rr2綁定到乘法結果上
}{// 不能將一個右值引用綁定到一個右值引用類型的變量上int&& rr1 = 42; // 正確:字面常量是右值//int&& rr2 = rr1; // 錯誤:表達式rr1是左值//int rr = &&rr1; // 不能將一個右值引用直接綁定到一個左值上int i = 5;int&& rr3 = std::move(i); // ok}// 調用一個返回引用的函數得到左值,其它返回類型得到右值std::string s{ "a value" };std::cout << s << std::endl; // a valueget_val(s, 0) = 'A';std::cout << s << std::endl; // A valuereturn 0;
}/
// reference: https://msdn.microsoft.com/en-us/library/f90831hc.aspx
int test_lvalue_rvalue_2()
{int i, j;int *p = new int;// Correct usage: the variable i is an lvalue.i = 7;// Incorrect usage: The left operand must be an lvalue (C2106).//7 = i; // C2106//j * 4 = 7; // C2106// Correct usage: the dereferenced pointer is an lvalue.*p = i;delete p;const int ci = 7;// Incorrect usage: the variable is a non-modifiable lvalue (C3892).//ci = 9; // C3892// Correct usage: the conditional operator returns an lvalue.((i < 3) ? i : j) = 7;return 0;
}//
// reference: http://www.bogotobogo.com/cplusplus/C11/4_C11_Rvalue_Lvalue.php
namespace {class cat {};int square(int x) { return x*x; }int square2(int& x) { return x*x; }
}int test_lvalue_rvalue_3()
{
{ // lvalue examplesint i = 7; // i: lvalueint *pi = &i; // i is addressablei = 10; // we can modify itclass cat {};cat c; // c is an lvalue for a user defined type
}{ // rvalue examplesint i = 7; // i: lvalue but 7 is rvalueint k = i + 3; // (i+3) is an rvalue//int *pi = &(i + 3); // error, it's not addressable//i + 3 = 10; // error - cannot assign a value to it//3 = i; // error - not assignablecat c;c = cat(); // cat() is an rvalue}{int sq = square(10); // square(10) is an rvalue
}{int i = 7;square(i); // OKsquare(7);//square2(7); // error, 7 is an rvalue and cannot be assigned to a reference
}{int a = 7; // a is lvalue & 7 is rvalueint b = (a + 2); // b is lvalue & (a+2) is rvalueint c = (a + b); // c is lvalue & (a+b) is rvalueint * ptr = &a; // Possible to take address of lvalue//int * ptr3 = &(a + 1); // Compile Error. Can not take address of rvalue
}return 0;
}//
// reference: http://en.cppreference.com/w/cpp/language/reference
namespace {
void double_string(std::string& s)
{s += s; // 's' is the same object as main()'s 'str'
}char& char_number(std::string& s, std::size_t n)
{return s.at(n); // string::at() returns a reference to char
}
}int test_lvalue_rvalue_4()
{// 1. Lvalue references can be used to alias an existing object (optionally with different cv-qualification):std::string s = "Ex";std::string& r1 = s;const std::string& r2 = s;r1 += "ample"; // modifies s// r2 += "!"; // error: cannot modify through reference to conststd::cout << r2 << '\n'; // prints s, which now holds "Example"// 2. They can also be used to implement pass-by-reference semantics in function calls:std::string str = "Test";double_string(str);std::cout << str << '\n';// 3. When a function's return type is lvalue reference, the function call expression becomes an lvalue expressionstd::string str_ = "Test";char_number(str_, 1) = 'a'; // the function call is lvalue, can be assigned tostd::cout << str_ << '\n';return 0;
}/
// reference: http://en.cppreference.com/w/cpp/language/reference
namespace {
void f(int& x)
{std::cout << "lvalue reference overload f(" << x << ")\n";
}void f(const int& x)
{std::cout << "lvalue reference to const overload f(" << x << ")\n";
}void f(int&& x)
{std::cout << "rvalue reference overload f(" << x << ")\n";
}
}int test_lvalue_rvalue_5()
{// 1. Rvalue references can be used to extend the lifetimes of temporary objects// (note, lvalue references to const can extend the lifetimes of temporary objects too, but they are not modifiable through them):std::string s1 = "Test";// std::string&& r1 = s1; // error: can't bind to lvalueconst std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime// r2 += "Test"; // error: can't modify through reference to conststd::string&& r3 = s1 + s1; // okay: rvalue reference extends lifetimer3 += "Test"; // okay: can modify through reference to non-conststd::cout << r3 << '\n';// 2. More importantly, when a function has both rvalue reference and lvalue reference overloads,// the rvalue reference overload binds to rvalues (including both prvalues and xvalues),// while the lvalue reference overload binds to lvalues:int i = 1;const int ci = 2;f(i); // calls f(int&)f(ci); // calls f(const int&)f(3); // calls f(int&&)// would call f(const int&) if f(int&&) overload wasn't providedf(std::move(i)); // calls f(int&&)// This allows move constructors, move assignment operators, and other move-aware functions// (e.g. vector::push_back() to be automatically selected when suitable.return 0;
}/
// reference: http://www.bogotobogo.com/cplusplus/C11/5_C11_Move_Semantics_Rvalue_Reference.php
namespace {
void printReference(int& value)
{std::cout << "lvalue: value = " << value << std::endl;
}void printReference(int&& value)
{std::cout << "rvalue: value = " << value << std::endl;
}int getValue()
{int temp_ii = 99;return temp_ii;
}
}int test_lvalue_rvalue_6()
{int ii = 11;printReference(ii);printReference(getValue()); // printReference(99);return 0;
}// reference: https://msdn.microsoft.com/en-us/library/dd293668.aspx
namespace {
template<typename T> struct S;// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.
template<typename T> struct S<T&> {static void print(T& t){std::cout << "print<T&>: " << t << std::endl;}
};template<typename T> struct S<const T&> {static void print(const T& t){std::cout << "print<const T&>: " << t << std::endl;}
};template<typename T> struct S<T&&> {static void print(T&& t){std::cout << "print<T&&>: " << t << std::endl;}
};template<typename T> struct S<const T&&> {static void print(const T&& t){std::cout << "print<const T&&>: " << t << std::endl;}
};// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{S<T&&>::print(std::forward<T>(t));
}// This function returns the constant string "fourth".
const std::string fourth() { return std::string("fourth"); }
}int test_lvalue_rvalue_7()
{// The following call resolves to:// print_type_and_value<string&>(string& && t)// Which collapses to:// print_type_and_value<string&>(string& t)std::string s1("first");print_type_and_value(s1);// The following call resolves to:// print_type_and_value<const string&>(const string& && t)// Which collapses to:// print_type_and_value<const string&>(const string& t)const std::string s2("second");print_type_and_value(s2);// The following call resolves to:// print_type_and_value<string&&>(string&& t)print_type_and_value(std::string("third"));// The following call resolves to:// print_type_and_value<const string&&>(const string&& t)print_type_and_value(fourth());return 0;
}} // namespace lvalue_rvalue_
? ? ? GitHub:https://github.com/fengbingchun/Messy_Test
總結
以上是生活随笔為你收集整理的C++/C++11中左值、左值引用、右值、右值引用的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Caffe中调用TensorRT提供的
- 下一篇: Banknote Dataset(钞票数