一. 左值和右值ios
(一)概述編程
1. 左值是通常指表達式結束後依然存在的持久化對象。右值指表達式結束時就再也不存在的臨時對象。便捷的判斷方法:能對錶達式取地址、有名字的對象爲左值。反之,不能取地址、匿名的對象爲右值。函數
2. C++ 表達式(運算符帶上其操做數、字面量、變量名等)可按照兩種獨立的屬性:類型和值類別 (value category)。而表達式的值類別必屬於左值、純右值或將亡值三者之一。如int&& x;當x用於表達式時,其類型爲右值引用,但值類別爲左值(由於有名字)spa
(二)右值的分類code
1. 純右值(prvalue):用於識別臨時變量和一些不與對象關聯的值。函數返回值爲非引用類型、表達式臨時值(如1+3)、lambda表達式等。對象
2. 將亡值(xvalue):是與右值引用相關的表達式,一般指將要被移動的對象。如,函數返回類型爲T&&、std::move的返回值、轉換爲T&&的類型轉換函數的返回值(注意,這些都是與右值引用相關的表達式)或臨時對象。blog
(三)表達式值類別的典型例子內存
|
左值表達式(lvalue)ci |
右值表達式(rvalue)字符串 |
|
純右值表達式(prvalue) |
將亡值表達式(xvalue) |
||
名字 |
由變量、函數或數據成員等名字構成的表達式仍爲左值表達式(注意,不論其類型)。如具名的右值引用爲左值表達式。 |
匿名對象。如匿名的右值引用。 |
|
函數調用或者重載運算符 |
①返回類型是左值引用的。 ②返回類型是到函數的右值引用。 |
返回類型是非引用類型。 |
返回類型爲對象的右值引用,例如 std::move(x) |
類型轉換表達式 |
①轉換爲左值引用類型,如static_cast<int&>(x) ②轉換爲函數的右值引用類型的轉型表達式,如 static_cast<void (&&)(int)>(x)也是左值表達式 |
轉換爲非引用類型的轉型表達式如 static_cast<double>(x)、(int)42等。 |
轉換爲對象的右值引用類型的轉型表達式,例如 static_cast<char&&>(x) |
對象成員表達式(a.m) |
①m爲靜態成員函數 ②a 爲左值且 m 爲非引用類型的非靜態數據成員 |
①m爲普通成員函數(只能用於函數調用,如a.m(10),不能用於初始化引用或做爲函數實參等其它用途) ②a 爲prvalue且 m 爲非引用類型的非靜態數據成員 |
a 是xvalue且 m 是非引用類型的非靜態數據成員 |
字面量 |
字符串字面量(如」hello world!」 |
除字符串字面量以外的字面量。如4二、true、nullptr |
無 |
其它 |
①內置賦值表達式(如a = b、a+= b等; ②間接尋址表達式(如*p) |
①內置算術、邏輯和比較表達式 ②內置取地址表達式(如&a) ③lambda表達式 |
臨時對象的表達式 |
2、左值引用和右值引用
(一)概述
1. 左值引用和右值引用都屬於引用類型。不管是聲明一個左值引用仍是右值引用都必須當即進行初始化。
2. 左值引用都是左值。但具名的右值引用是左值,而匿名的右值引用是右值。
(二)可綁定的值類型(設T是個具體類型)
1. 左值引用(T&):只能綁定到左值(很是量左值)
2. 右值引用(T&&):只能綁定到右值(很是量右值)
3. 常量左值引用(const T&):常量左值引用是個「萬能」的引用類型。它既能夠綁定到左值也能夠綁定到右值。它像右值引用同樣能夠延長右值的生命期。不過相比於右值引用所引用的右值,常量左值引用的右值在它的「餘生」中只能是隻讀的。
4. 常量右值引用(const T&&):可綁定到右值或常量右值。因爲移動語義須要右值能夠被修改,所以常量右值引用沒有實際用處。若是須要引用右值且讓其不可更改,則常量左值引用就足夠了。
【編程實驗】左值引用和右值引用
#include <iostream> #include <vector> using namespace std; class Widget { public: int x; int& rx = x; int arr[10]; static int staticfunc(int x) { cout << "static int Widget::staticfunc(int x): " <<x << endl; return x; } int commonfunc(int x) { cout << "int Widget::commonfunc(int): " << x << endl; return x; } }; Widget makeWidgetR() { return Widget(); } Widget& makeWidgetL() { static Widget w; return w; //ok, Widget&是個引用類型,要注意不能返回局部對象。 } Widget&& makeWidgetX() { static Widget w; return std::move(w); //ok。但要注意,Widget&&是個引用類型不能返回局部對象。 } //返回到函數的引用類型 using RetFunc = int(int); int demoImpl(int i) { cout << "int demo(int): " << i << endl; return i; } RetFunc&& RetFuncDemo() { return demoImpl; } int main() { //1. 常見的左/右值表達式分析 //1.1 函數形參爲左值 //int test(int&& x){return x;} //形參x爲左值(具名變量),儘管其爲右值引用類型。 int i = 0; //1.2 前置/後置自增、自減表達式 int&& ri = i++; //i++爲右值,表達式返回的是i的拷貝,匿名對象是個右值 int& li = ++i; //++i返回i自己,是個具名對象,爲左值。 int& r2 = ri; //雖然ri的類型是int&&,但ri自己是個具名變量。所以仍爲左值。 //int&& r3 = ri; //error,ri是個左值 r2 = 5; cout << "ri = " << ri << ", i = "<< i << endl; //5, 2 //1.3 解引用和取地址運算表達式 int* p = &i; int& lp = *p; //解引用:*p爲左值,由於能夠對*p取址址&(*p)。或*p = 5; int*&& rp = &i; //取地址:&i是個內存地址,是個右值。能夠用來初始化右值引用 *rp = 10; cout <<"i = "<< i << endl; //i = 10,i的值經過rp引用修改。 //1.4 字面量 const char(&hw)[13] = "hello world!"; //字符串字面量是左值,能夠用於初始化左值引用 cout << "const char(&hw)[13] = " << hw << endl; int&& ten = 10; //10爲純右值 //1.5 賦值表達式 和 算術表達式、比較表達式、邏輯表達式 int& a = (i += 2); //i +=2爲賦值表達式,結果爲左值。相似的,還有 a = b、a %= b int&& b = i + 2; //i+2爲算術表達式,結果爲右值。相似的還有a + b、a % b、a & b、a << b int&& c = (a > b); //比較表達式結果爲右值。相似的還有:a < b、a == b、a >= b int&& d = (a && b); //邏輯表達式結果爲右值。相似的還有:a && b、a || b、!a //1.6 lambda表達式 //auto& lam = [](int x, int y) {return x + y; };//lambda表達式爲純右值,不能綁定到左值 //2.下標表達式 int arr[10]; int& ra1 = arr[2]; //[]下標表達式返回左值引用,還是左值 vector<int> vec{ 1,2,3,4 }; int& rv = vec[2]; //operator[]返回左值引用,是個左值表達式。 //3.對象訪問表達式 Widget w1; int& rx1 = w1.x; //w1爲左值,因此w1.x爲左值 int&& rx2 = Widget().x; //Widget()是個臨時對象(右值)。所以,Widget().x爲右值。 //int& rx3 = Widget().x; //error,理由同上。 using WidgetStaticFunc = int(int); WidgetStaticFunc& wsf = w1.staticfunc; //靜態成員函數,是個左值。 wsf(10); w1.commonfunc(2); //w1.commomfunc是個純右值,只能用於函數調用,不能作其它用途。 //4. 類型轉換表達式 int&& i1 = std::move(i); //std::move()返回值爲右值引用類型,是個右值。 int&& i2 = static_cast<int&&>(i); //轉換爲右值引用類型,表達式結果是個右值 double&& d1 = static_cast<double>(i); //轉換爲右值類型,結果是個右值 int& i3 = static_cast<int&>(i); //轉換爲左值引用類型,表達式結果爲左值 //5.函數返回型類型 Widget& w2 = makeWidgetL(); //返回左值引用類型,爲左值表達式 Widget&& w3 = makeWidgetR(); //返回非引用類型,爲右值表達式 Widget&& w4 = makeWidgetX(); //返回右值引用類型,爲右值表達式 RetFunc& rf1 = RetFuncDemo(); //RetFuncDemo返回一個到函數的右值引用,是個左值表達式(C++11的標準行爲)。 RetFunc&& rf2 = RetFuncDemo(); //RetFuncDemo仍能夠用來初始化右值引用! rf1(5); return 0; } /*輸出結果 ri = 5, i = 2 i = 10 const char(&hw)[13] = hello world! static int Widget::staticfunc(int x): 10 int Widget::commonfunc(int): 2 int demo(int): 5 */
3、萬能引用(universal reference)
(一)T&&的含義
1. 當T是一個具體的類型時,T&&表示右值引用,只能綁定到右值。
2. 當涉及T類型推導時,T&&爲萬能引用。若用右值初始化萬能引用,則T&&爲右值引用。若用左值初始化萬能引用,則T&&爲左值引用。但無論哪一種狀況,T&&都是一種引用類型。
(二)萬能引用
1. T&&是萬能引用的兩個條件:
(1)必須涉及類型推導;
(2)聲明的形式也必須正好形如「T&&」。而且該形式被限定死了,任何對其修飾都將剝奪T&&成爲萬能引用的資格。
2. 萬能引用使用的場景
(1)函數模板形參
(2)auto&&
【編程實驗】萬能引用
#include <iostream> #include <vector> using namespace std; class Widget {}; void func1(Widget&& param) {}; //param爲右值引用類型(不涉及類型推導) template<typename T> void func2(T&& param){} //param爲萬能引用(涉及類型推導) template<typename T> void func3(std::vector<T>&& param) {} //param爲右值引用,由於形式不是正好T&& //param的類型己肯定爲vector類型,而推導的是其元素的類型, //而不是param自己的類型。 template<typename T> void func4(const T&& param){} //param是個右值引用,由於被const修飾,其類型爲const T&&,而不符」正好是T&&」的要求 template<class T> class MyVector { public: void push_back(T&& x){} //x爲右值引用。由於當定義一個MyVector對象後,T己肯定。當調用該函數時T的類型不用再推導! //如MyVector<Widget> v; v.push_back(...);時T己經是肯定的Widget類型,無須再推導。 template<class...Args> void emplace_back(Args&& ... args) {}; //args爲萬能引用,由於Args獨立於T的類型,當調用該函數時,需推導Args的類型。 }; int main() { //1. 模板函數形參(T&&) Widget w; func2(w); //func2(T&& param),param爲Widget&(左值引用) func2(std::move(w)); //param爲Widget&&,是個右值引用。 //2. auto&& int x = 0; Widget&& var1 = Widget(); //var1爲右值引用(不涉及類型推導) auto&& var2 = var1; //萬能引用,auto&&被推導爲Widget&&(右值引用) auto&& var3 = x; //萬能引用,被推導爲int&;(左值引用) //3. 計算任意函數的執行時間:auto&&用於lambda表達式形參(C++14) auto timefunc = [](auto && func, auto && ... params) { //計時器啓動 //調用func(param...)函數 std::forward<decltype(func)>(func)( //根據func的左右值特性來調用相應的重載&或&&版本的成員函數 std::forward<decltype(params)>(params)... //保持參數的左/右值特性 ); //計時器中止並記錄流逝的時間 }; timefunc(func1, std::move(w)); //計算func1函數的執行時間 return 0; }