在C
中對(duì)于空類編譯器會(huì)生成一些默認(rèn)的成員函數(shù),比如:構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、運(yùn)算符重載、析構(gòu)函數(shù)、&和const&的重載、移動(dòng)構(gòu)造、移動(dòng)拷貝構(gòu)造等函數(shù)。
如果在類中顯式定義了,編譯器將不會(huì)重新生成默認(rèn)版本。有時(shí)候這樣的規(guī)則可能被忘記,最常見(jiàn)的是聲明了帶參數(shù)的構(gòu)造函數(shù),必要時(shí)則需要定義不帶參數(shù)的版本以實(shí)例化無(wú)參的對(duì)象。而且有時(shí)編譯器會(huì)生成,有時(shí)又不生成,容易造成混亂,于是C 11
讓程序員可以控制是否需要編譯器生成。
在C 11
中,可以在默認(rèn)函數(shù)定義或者聲明時(shí)加上=default
,從而顯式的指示編譯器生成該函數(shù)的默認(rèn)版本,用=default
修飾的函數(shù)稱為顯式缺省函數(shù)。
class A{public: A(int a) : _a(a) {} // 顯式缺省構(gòu)造函數(shù),由編譯器生成 A() = default; // 可以選擇在類中聲明,在類外定義時(shí)讓編譯器生成默認(rèn)賦值運(yùn)算符重載 A& operator=(const A& a);private: int _a;};A& A::operator=(const A& a) = default; //類外定義int main(){ A a1(10); A a2; a2 = a1; return 0;}
如果能想要限制某些默認(rèn)函數(shù)的生成:
C 98
中,是該函數(shù)設(shè)置成private
,并且不完成實(shí)現(xiàn),這樣只要其他人想要調(diào)用就會(huì)報(bào)錯(cuò)。C 11
中更簡(jiǎn)單,只需在該函數(shù)聲明加上=delete
即可,該語(yǔ)法指示編譯器不生成對(duì)應(yīng)函數(shù)的默認(rèn)版本,稱=delete
修飾的函數(shù)為刪除函數(shù)。class A{public: A(int a) : _a(a) {} // 禁止編譯器生成默認(rèn)的拷貝構(gòu)造函數(shù)以及賦值運(yùn)算符重載 A(const A&) = delete; A& operator(const A&) = delete;private: int _a;};int main(){ A a1(10); A a2(a1); // 編譯失敗,因?yàn)樵擃悰](méi)有拷貝構(gòu)造函數(shù) A a3(10); a3 = a2; // 編譯失敗,因?yàn)樵擃悰](méi)有賦值運(yùn)算符重載 return 0;}
如果一個(gè)類中涉及到資源管理,用戶必須顯式提供拷貝構(gòu)造、賦值運(yùn)算符重載以及析構(gòu)函數(shù),否則編譯器將會(huì)自動(dòng)生成一個(gè)默認(rèn)的,如果遇到拷貝對(duì)象或者對(duì)象之間相互賦值,就會(huì)出錯(cuò),比如:
class String{public: String(char* str = ""){ if (nullptr == str) str = ""; _str = new char[strlen(str) 1]; strcpy(_str, str); } String(const String& s) : _str(new char[strlen(s._str) 1]) { strcpy(_str, s._str); } String& operator=(const String& s){ if (this != &s){ char* pTemp = new char[strlen(s._str) 1]; strcpy(pTemp, s._str); delete[] _str; _str = pTemp; } return *this; } ~String(){ if (_str) delete[] _str; }private: char* _str;};
假設(shè)現(xiàn)在有一個(gè)函數(shù),返回值為一個(gè)String
類型的對(duì)象:
String GetString(char* pStr){ String strTemp(pStr); return strTemp; //此時(shí)不是返回棧上對(duì)象strTemp,而是拷貝構(gòu)造一個(gè)臨時(shí)對(duì)象返回}int main(){ String s2(GetString("world")); /* 用GetString返回的臨時(shí)對(duì)象構(gòu)造s2 s2構(gòu)造完成后臨時(shí)對(duì)象將被銷毀,因?yàn)榕R時(shí)對(duì)象臨時(shí)對(duì)象不能直接返回 因此編譯器需要拷貝構(gòu)造一份臨時(shí)對(duì)象,然后將strTemp銷毀 */ return 0;}
上述代碼看起來(lái)沒(méi)有什么問(wèn)題,但是有一個(gè)不太盡人意的地方:GetString
函數(shù)返回的臨時(shí)對(duì)象,將s2
拷貝構(gòu)造成功之后,立馬被銷毀了(臨時(shí)對(duì)象的空間被釋放),再?zèng)]有其他作用;
而s2
在拷貝構(gòu)造時(shí),又需要分配空間,一個(gè)剛釋放一個(gè)又申請(qǐng),有點(diǎn)多此一舉。
那能否將GetString
返回的臨時(shí)對(duì)象的空間直接交給s2
呢?這樣s2
也不需要重新開(kāi)辟空間了,代碼的效率會(huì)明顯提高。
C 11
中如果需要實(shí)現(xiàn)移動(dòng)語(yǔ)義,必須使用右值引用。String(String&& s) //兩個(gè) & : _str(s._str) { s._str = nullptr; }
右值引用,顧名思義就是對(duì)右值的引用。C 11
中,右值由兩個(gè)概念組成:純右值和將亡值。
C 98
中右值的概念,用于識(shí)別臨時(shí)變量和一些不跟對(duì)象關(guān)聯(lián)的值。右值引用書(shū)寫(xiě)格式:
類型&& 引用變量名字 = 實(shí)體;
右值引用最長(zhǎng)常見(jiàn)的一個(gè)使用地方就是:與移動(dòng)語(yǔ)義結(jié)合,減少無(wú)必要資源的開(kāi)辟來(lái)提高代碼的運(yùn)行效率。
改造一下剛才的例子代碼演示:
String&& GetString(char* pStr){ String strTemp(pStr); return strTemp;}int main(){ String s1("hello"); String s2(GetString("world")); return 0;}
右值引用另一個(gè)比較常見(jiàn)的地方是:給一個(gè)匿名對(duì)象取別名,延長(zhǎng)匿名對(duì)象的聲明周期。
String GetString(char* pStr){ return String(pStr);}int main(){ String&& s = GetString("hello"); return 0;}
【注】:
int main(){ int a = 10; int&& ra; // 編譯失敗,沒(méi)有進(jìn)行初始化 int&& ra = a; // 編譯失敗,a是一個(gè)左值 const int&& ra = 10; // ra是匿名常量10的別名 return 0;}
C 11
中,std::move()
函數(shù)位于<utility>
頭文件中,這個(gè)函數(shù)名字具有迷惑性,它并不搬移任何東西,唯一的功能就是將一個(gè)左值強(qiáng)制轉(zhuǎn)化為右值引用,通過(guò)右值引用使用該值,實(shí)現(xiàn)移動(dòng)語(yǔ)義。
注意:被轉(zhuǎn)化的左值,其生命周期并沒(méi)有隨著左右值的轉(zhuǎn)化而改變,即std::move
轉(zhuǎn)化的左值變量left_value
不會(huì)被銷毀。
move()
誤用的例子:// 移動(dòng)構(gòu)造函數(shù)class String{ String(String&& s) : _str(s._str){ s._str = nullptr; }};int main(){ String s1("hello world"); String s2(move(s1)); String s3(s2); return 0;}
move()
更多的是用在生命周期即將結(jié)束的對(duì)象上。
【注】:為了保證移動(dòng)語(yǔ)義的傳遞,程序員在編寫(xiě)移動(dòng)構(gòu)造函數(shù)時(shí),最好使std::move
轉(zhuǎn)移擁有資源的成員為右值。
String(const String&&);const Person GetTempPerson();
C 11
中,無(wú)參構(gòu)造函數(shù) / 拷貝構(gòu)造函數(shù) / 移動(dòng)構(gòu)造函數(shù)實(shí)際上有3
個(gè)版本:Object();Object(const T&);Object(T &&);
C 11
中默認(rèn)成員函數(shù)C 11
中,拷貝構(gòu)造/移動(dòng)構(gòu)造/賦值/移動(dòng)賦值函數(shù)必須同時(shí)提供,或者同時(shí)不提供,程序才能保證類同時(shí)具有拷貝和移動(dòng)語(yǔ)義。完美轉(zhuǎn)發(fā)是指:在函數(shù)模板中,完全依照模板的參數(shù)的類型,將參數(shù)傳遞給函數(shù)模板中調(diào)用的另外一個(gè)函數(shù)。
void Func(int x){ // ......}template<typename T>void PerfectForward(T t){ Fun(t);}
PerfectForward
為轉(zhuǎn)發(fā)的模板函數(shù),Func
為實(shí)際目標(biāo)函數(shù),但是上述轉(zhuǎn)發(fā)還不算完美:
完美轉(zhuǎn)發(fā)是:目標(biāo)函數(shù)總希望將參數(shù)按照<傳遞給轉(zhuǎn)發(fā)函數(shù)的實(shí)際類型>轉(zhuǎn)給目標(biāo)函數(shù),而不產(chǎn)生額外的開(kāi)銷,就好像轉(zhuǎn)發(fā)者不存在一樣。
所謂完美:函數(shù)模板在向其他函數(shù)傳遞自身形參時(shí),如果相應(yīng)實(shí)參是左值,它就應(yīng)該被轉(zhuǎn)發(fā)為左值;如果相應(yīng)實(shí)參是右值,它就應(yīng)該被轉(zhuǎn)發(fā)為右值。這樣做是為了保留在其他函數(shù)針對(duì)轉(zhuǎn)發(fā)而來(lái)的參數(shù)的左右值屬性進(jìn)行不同處理(比如參數(shù)為左值時(shí)實(shí)施拷貝語(yǔ)義;參數(shù)為右值時(shí)實(shí)施移動(dòng)語(yǔ)義)。
C 11
通過(guò)forward
函數(shù)來(lái)實(shí)現(xiàn)完美轉(zhuǎn)發(fā), 比如:
void Fun(int &x) { cout << "lvalue ref" << endl; }void Fun(int &&x) { cout << "rvalue ref" << endl; }void Fun(const int &x) { cout << "const lvalue ref" << endl; }void Fun(const int &&x) { cout << "const rvalue ref" << endl; }template<typename T>void PerfectForward(T &&t) { Fun(std::forward<T>(t)); }int main(){ PerfectForward(10); // rvalue ref int a; PerfectForward(a); // lvalue ref PerfectForward(std::move(a)); // rvalue ref const int b = 8; PerfectForward(b); // const lvalue ref PerfectForward(std::move(b)); // const rvalue ref return 0;}
感謝您閱讀至此,感興趣的看官們可以移步上篇與下篇,繼續(xù)了解C 11
剩余新特性~
【從零學(xué)C 11(上)】
列表初始化
、decltype
關(guān)鍵字、委派構(gòu)造
等新特性
【https://blog.csdn.net/qq_42351880/article/details/100140163】
來(lái)源:https://www.icode9.com/content-1-423301.html【從零學(xué)C 11(下)】
lambda
表達(dá)式、線程庫(kù)
、原子操作庫(kù)
等新特性
【https://blog.csdn.net/qq_42351880/article/details/100144882】
聯(lián)系客服