——原創,引用請附帶博客地址
2019-12-06 23:42:18 這篇文章分析的還是不行,先暫時放在這以后再更新。
本篇比較長,需要耐心閱讀
以一個實際問題開始分析
class Sub{} Sub GetInstance(){ return Sub(); } int main(){ Sub a=GetInstance(); .... ////////////////////////////////////////////////////////////////////
【分析】:例子比較簡易,主要通過GetInstance方法返回一個Sub實例,在這個過程中,發生的事情如下:
- GetInstance內部執行Sub()構造一次,由於是臨時變量,在函數結束后被析構。
- 返回的臨時變量又克隆一份給返回值,這個過程調用一次復制構造函數。
- 在main函數中,由於賦值又調用了一次復制構造函數。
整個過程執行了三次構造(包括復制構造),最關鍵的問題在於,在GetInstance()方法中第一次構造的Sub實例在函數結束后被析構,存在的意義只在於被復制構造給返回值。
又假定,如果Sub對象非常巨大,那么得到實例a的代價非常巨大。整個來看,函數內構造的Sub實例沒有太大意義,一個設想是能不能把這個Sub實例保存下來,這樣至少節省了一次構造。換句話說,能不能把將要消失的臨時對象保存下來,延長它的生命周期?
左值右值定義
一般說法,可以取地址的,有名字的是左值,反之,不能取地址,沒有名字的是右值。
比如:a=b+c;
&a是允許的操作,而&(b+c)不能通過編譯,因此a是一個左值,而(b+c)是一個右值。
C++11中,右值由兩個概念組成,一個是將亡值,另一個是純右值。
純右值
純右值是C++98中標准中右值的概念,主要用於辨識臨時變量和一些不跟對象關聯的值。
(*注:不跟對象關聯,這段話非常特別,后面再解釋)
比如:非引用返回的函數返回的臨時變量值就是純右值。
1. 一些運算表達式,比如1+3產生的臨時變量值為純右值。
2. 不跟對象關聯的字面量值,比如2、'c'、true等,也是純右值。
將亡值
將亡值是C++11新增的跟右值引用相關的表達式,這種表達式通常是將被移動的對象(移為他用),比如返回右值引用T &&的函數返回值、std::move的返回值(后面詳細解釋)
在C++11的程序中,所有的值必屬於左值、將亡值、純右值三者之一
右值引用
一個小例子說明
int && Add(){ return 1+2; }
【分析】:方法Add返回一個右值,一般情況下,此處的右值隨着方法結束而消失,但是通過右值引用,可以將生命周期延續,此處的 int &&就是右值引用。
左右值引用的綁定
一般來說,右值引用不能綁定左值,比如:
int a=10;
int &&b=a;
此處不能通過。
那么,左值引用能不能綁定右值呢?
int && Add(){ //右值引用
return 1+2;
}
int &a=Add();
此處不能通過編譯,但是有一個例外,像下面這種情形是可以的。
const int &a=Add();
唯獨多了一個const,這個const很神奇,下面詳細解釋一下const
const
被const修飾的常量左值引用,可以接納非常量左值、常量左值、右值對其進行初始化,是什么意思呢?下面以例子說明
void Add(const int &a,const int &b){ std::cout<<a+b<<std::endl; }
【分析】:Add方法的形參是兩個const左值引用,因此在調用時分為三種情形:
1.
const int a=1;
const int b=2;
Add(a,b); //這種屬於常量左值入參
2.
int a=1;
int b=2;
Add(a,b); //這種屬於非常量左值入參
3.
Add(1,2); //這種屬於右值,直接拿右值1,2進行調用
尤其對於第3種情形,如果Add的形參不是常量左值引用,比如 Add(int a,int b),那么第3種情形不能通過。
可以看出,const常量左值引用接納能力非常強,算是“萬能”引用類型。因此,const左值引用可以綁定右值
比如:const int & a=10;完全可行,
但是,常量左值引用有一個缺點,就是其生存周期中只能是“只讀的”。
函數返回值究竟是什么是類型?
首先將前面的分析做個階段總結
1. 右值引用不能綁定左值
2. 左值引用綁定右值分為兩種情況:
(1) 如果是非const的左值引用只能綁定左值
(2) const常量左值引用幾乎是萬能的,可以綁定常量、非常量左值,或右值
下面看個例子:
class Sub{...}
Sub GetInstance(){
return Sub();
}
Sub &sub=GetInstance(); //可以通過編譯
【分析】:GetInstance()返回的究竟是哪種類型呢?
如果是右值,根據前面的結論,非常量左值引用是不可以綁定右值的,但結果是編譯可以通過。
這個問題很神奇,現在的結論是:函數返回的都是右值。
回到最前面的右值定義:
這里提到一句,不跟對象關聯的值,但是上述GetInstance方法返回的是類Sub的實例,所以,這里算不算是特殊對待呢?
將VS的警告級別提升,可以看到如下警告:
從警告中可以推斷出,函數返回的確實是右值,而編譯器在這里使用了“非標准擴展初始化”才會使這一句通過。
結論如下:
函數返回的是右值,但是和對象有關聯的右值,似乎規則放松了一點,可以被非常量左值引用綁定,使用的是【非標准擴展初始化】
12232