C++11
- 前言
- 列表初始化
- {}進行初始化
- initializer_list
- 右值引用和移動語義
- 左值與右值
- 左值引用與右值引用
- 引用延長生命周期
- 右值引用和移動語義的使用場景
- 左值引用
- 移動構造和移動賦值
- 右值引用在容器插入的提效
- 引用折疊
- 萬能折疊
- 完美轉發
前言
C++11是C++繼98后的更新,其更新了許多內容,是非常重要的,除了我們前文所提到的范圍for和auto,還有許多新的東西,對于C++11完全可以把它當成新的語言進行學習
列表初始化
{}進行初始化
C++11以后想統?初始化?式,試圖實現?切對象皆可?{}初始化,{}初始化也叫做列表初始化。
內置類型?持,?定義類型也?持,?定義類型本質是類型轉換,中間會產?臨時對象,最后優化了以后變成直接構造。
{}初始化的過程中,可以省略掉=
int x2 { 2 };
Date d6 { 2024, 7, 25 };
C++11列表初始化的本意是想實現?個?統?的初始化?式,其次他在有些場景下帶來的不少便利,如容器push/inset多參數構造的對象時,{}初始化會很方便
initializer_list
initializer_list是C++11之后提到的一個類,這個類的本質是底層開?個數組,將數據拷貝過來,initializer_list內部有兩個指針分別指向數組的開始和結束。initializer_list與剛剛提到的{}初始化是不一樣的,{}是基于構造函數,也就是說需要初始化每一個值都是需要對應的構造函數的。這樣對于初始化容器就很不方便,例如初始化vector里的數,總不能寫N個構造函數吧
vector<int> v1 ={1,2,3}
容器?持?個std::initializer_list的構造函數,也就?持任意多個值構成的 {x1,x2,x3…} 進?初始化。STL中的容器?持任意多個值構成的 {x1,x2,x3…} 進?初始化,就是通過initializer_list的構造函數?持的。
右值引用和移動語義
左值與右值
左值是?個表?數據的表達式(如變量名或解引用的指針),?般是有持久狀態,存儲在內存中,我們可以獲取它的地址,左值可以出現賦值符號的左邊,也可以出現在賦值符號右邊。定義時const修飾符后的左值,不能給他賦值,但是可以取它的地址。
右值也是?個表?數據的表達式,要么是字面值常量、要么是表達式求值過程中創建的臨時對象等,右值可以出現在賦值符號的右邊,但是不能出現出現在賦值符號的左邊,右值不能取地址。
左值與右值的核心區別就是能否取地址
左值引用與右值引用
int& a = x;int&& b = y;
第一個語句就是左值引用,第二個就是右值引用。
左值引用不能直接引用右值,但是const左值引用可以引用右值
右值引用不能直接引用左值,但是右值引用可以引用move(左值)
move的作用就是強制轉化為右值,但是屬性還是左值的屬性,只是類型強制轉換。但是除非確定需要將左值轉為右值,否則不要用move,因為move之后左值的資源會被竊取掉,那么左值又是會一直存在的,就會導致左值是一個懸空指針
需要注意的是變量表達式都是左值屬性,也就意味著?個右值被右值引?綁定后,右值引?變量變量表達式的屬性是左值,不然在后續進行移動語義的時候,如果右值引用后的屬性是右值屬性,那就無法修改了,也就無法移動了。
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
double x = 1.1, y = 2.2;
// 左值引?給左值取別名
int& r1 = b;
int*& r2 = p;
int& r3 = *p;
string& r4 = s;
char& r5 = s[0];
// 右值引?給右值取別名
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = min(x, y);
string&& rr4 = string("11111");
// 左值引?不能直接引?右值,但是const左值引?可以引?右值
const int& rx1 = 10;
const double& rx2 = x + y;
const double& rx3 = min(x, y);
const string& rx4 = string("11111");
// 右值引?不能直接引?左值,但是右值引?可以引?move(左值)
int&& rrx1 = move(b);
int*&& rrx2 = move(p);
int&& rrx3 = move(*p);
string&& rrx4 = move(s);
string&& rrx5 = (string&&)s;
引用延長生命周期
右值引?可?于為臨時對象延??命周期,const 的左值引?也能延?臨時對象?存期,但const對象?法被修改。
右值引用和移動語義的使用場景
左值引用
左值引?主要使?場景是在函數中左值引?傳參和左值引?傳返回值時減少拷?,同時還可以修改實參和修改返回對象的值。左值引?已經解決?多數場景的拷?效率問題,但是有些場景不能使?傳左值引?返回,如addStrings和generate函數,C++98中的解決?案只能是被迫使?輸出型參數解決。那么C++11以后這?可以使?右值引?做返回值解決嗎?顯然是不可能的,因為這?的本質是返回對象是?個局部對象,函數結束這個對象就析構銷毀了,右值引?返回也?法改變對象已經析構銷毀的事實。
移動構造和移動賦值
移動構造函數是?種構造函數,類似拷?構造函數,移動構造函數要求第?個參數是該類類型的引?,但是不同的是要求這個參數是右值引?,如果還有其他參數,額外的參數必須有缺省值。
移動賦值是?個賦值運算符的重載,他跟拷?賦值構成函數重載,類似拷?賦值函數,移動賦值函數要求第?個參數是該類類型的引?,但是不同的是要求這個參數是右值引?。
對于像string/vector這樣的深拷貝的類或者包含深拷貝的成員變量的類,移動構造和移動賦值才有意義,因為移動構造和移動賦值的第?個參數都是右值引?的類型,他的本質是要“竊取”引?的右值對象的資源,?不是像拷?構造和拷?賦值那樣去拷?資源,從提?效率。
必須得有移動構造函數才可以出現移動構造
string(string&& s)
{cout << "string(const string& s) -- 移動構造" << endl;this->swap(s);
}
移動構造是將右值與當前值進行交換,將有用的資源交換給需要的變量,這樣相比與之前的拷貝構造出一個臨時變量,再將臨時變量拷貝構造給所需要的變量,效率要高很多
右值引用在容器插入的提效
? 查看STL文檔我們發現C++11以后容器的push和insert系列的接?否增加的右值引?版本
? 當實參是?個左值時,容器內部繼續調?拷貝構造進?拷貝,將對象拷貝到容器空間中的對象
? 當實參是?個右值,容器內部則調?移動構造,右值對象的資源到容器空間的對象上
引用折疊
? C++中不能直接定義引?的引?如 int& && r = i; ,這樣寫會直接報錯,通過模板或 typedef中的類型操作可以構成引用的引用。
? 通過模板或 typedef 中的類型操作可以構成引用的引用時,這時C++11給出了?個引用折疊的規則:右值引用的右值引用折疊成右值引用,所有其他組合均折疊成左值引用。
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n; // r1 的類型是 int&
lref&& r2 = n; // r2 的類型是 int&
rref& r3 = n; // r3 的類型是 int&
rref&& r4 = 1; // r4 的類型是 int&&
萬能折疊
template<class T>
void Function(T&& t)
{}
以上代碼就是萬能折疊,如果傳的是int右值,T就會被識別成int,如果傳的是int左值,T就會被識別成int&,這樣就實現了傳左值就是左值引用的參數,傳右值就是右值引用的參數
完美轉發
因為變量表達式的屬性都是左值,即雖然傳的是右值,在萬能折疊下,t被識別成右值引用的類型,但由于t是變量表達式,所以如果把t傳給其他函數,就會把其他函數的形參的屬性變為左值,所以出現完美轉發來解決這一問題。
template<class T>
void Function(T&& t)
{// 完美保持他的屬性傳參Fun(forward<T>(t));
}
完美轉發forward是一個函數模版,可以把他與move相對應,move是將左值強制轉化為右值,但是屬性并不會發生改變,只是告訴編譯器該左值是可以被竊取的,而forward是可以保持該對象的屬性的,即t如果是右值引用,就會保持為右值引用,如果是左值,就會保持為左值。