??作者:一只大喵咪1201
??專欄:《C++學(xué)習(xí)》
??格言:你只管努力,剩下的交給時間!
C++的發(fā)展截至到目前為止,雖然版本有很多,但是C++11則帶來了數(shù)量可觀的變化,其中包含了約140個新特性,以及對C++03標(biāo)準(zhǔn)中約600個缺陷的修正,這使得C++11更像是從C++98/03中孕育出的一種新語言。相比較而言,C++11能更好地用于系統(tǒng)開發(fā)和庫開發(fā)、語法更加泛華和簡單化、更加穩(wěn)定和安全,不僅功能更強大,而且能提升程序員的開發(fā)效率,公司實際項目開發(fā)中也用得比較多,所以我們要作為一個重點去學(xué)習(xí)。
??列表初始化
- 列表:{ }就被叫做列表。
我們之前使用列表初始化都是這樣的,如上圖代碼所示,可以使用列表來初始化數(shù)組,初始化結(jié)構(gòu)體變量,初始化元素類型為結(jié)構(gòu)體變量的數(shù)組等等。
- C++11擴大了用大括號括起的列表(初始化列表)的使用范圍,使其可用于所有的內(nèi)置類型和用戶自定義的類型,使用初始化列表時,可添加等號(=),也可不添加。
可以不加等會進(jìn)行初始化,如上圖代碼所示,但是強烈不建議使用。
不寫等號列表初始化的正確用法:
正確的用法應(yīng)該是在new一個對象,一個數(shù)組并且對它們進(jìn)行初始化的時候使用,如上圖所示。
簡單寫一個日期類,在構(gòu)造函數(shù)中打印一句話表面構(gòu)造函數(shù)被調(diào)用過。
我們一直使用的都是C++98中方式初始化,如上圖代碼中的第一種方式,C++11中提供的列表初始化如上圖后兩種方式,這些都是在調(diào)用構(gòu)造函數(shù)來初始化。
這其實也很雞肋,沒有什么價值,繼續(xù)使用C++98中的方式就挺好的,而且容易理解,C++11中的方式反而不太好理解了。
?? std::initializer_list
用初始化列表來初始化STL容器,如上圖所示初始化vector,list,map等,當(dāng)然等號可以去掉(強烈不建議)。
- vector和list以及map等STL中的容器也可以像普通數(shù)組一樣使用初始化列表來初始化了。
這是因為列表初始化本身就是一個類模板:
如上圖所示,這是C++11才有的一個類型,該類型叫做列表初始化,而且還有自己的成員函數(shù),包括構(gòu)造函數(shù),計算列表大小的接口,獲取列表起始和結(jié)束位置的接口(迭代器位置)。
創(chuàng)建一個列表,使用typeid().name()將類型打印出來,如上圖所示。
列表中不僅可以放內(nèi)置類型,還可以放自定義類型,如上圖所示。
- 列表中的自定義類型會調(diào)用它的構(gòu)造函數(shù),構(gòu)造出的對象組成列表。
列表也相當(dāng)于一個容器。
那么為什么可以用列表來初始化vector,list等容器呢?
C++11為這些容器提供了新的構(gòu)造函數(shù),如上圖所示。
- 該構(gòu)造函數(shù)是使用列表來初始化對象的,它的形參就是initializer_list。
vector(std::initializer_list<T> il)
{
//列表也是存在迭代器的
for(auto& e : il)
{
push_bakc(e);
}
}
其代碼實現(xiàn)如上。list,map等其他容器也是這個道理,都提供了一個用列表初始化的構(gòu)造函數(shù)。
賦值運算符重載函數(shù)也有一個列表的重載版本,如上圖所示。
可以用列表直接給vector賦值,list等其他容器也一樣可以。
對于列表初始化:
- 省略等號的用法只建議在new對象的時候使用,其他時候要加上等號。
- 使用列表不僅能夠初始化數(shù)結(jié)構(gòu),還可以初始化STL中的容器。
??新語法
C++11提供了一些新語法,這一小結(jié)中本喵來介紹一下比較小的語法,很多我們都接觸過甚至是使用過。
??聲明
c++11提供了多種簡化聲明的方式,尤其是在使用模板時。
auto
- auto能自動類型推斷,要求必須進(jìn)行顯示初始化,讓編譯器將定義對象的類型設(shè)置為初始化值的類型。
這個關(guān)鍵字我們已經(jīng)使用過很多了,這里就不再詳細(xì)解釋了,如:
可以自動推演出類型,使用起來非常方便。
decltype:
關(guān)鍵字decltype將變量的類型聲明為表達(dá)式指定的類型。
使用typeid().name()只能打印出類型的名稱,并不能用這個名稱繼續(xù)創(chuàng)建變量,而decltype可以:
使用decltype可以自動推演類型,并且可以用推演出的結(jié)果繼續(xù)創(chuàng)建變量,如上圖所示,對于一些不同類型直接的運算結(jié)果,decltype有奇效。
nullptr
由于C++中NULL被定義成字面量0,這樣就可能回帶來一些問題,因為0既能指針常量,又能表示整形常量。所以出于清晰和安全的角度考慮,C++11中新增了nullptr,用于表示空指針。
- 在C語言中,NULL是(void*)0,仍然是一個指針。
在C++中存在條件編譯:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
說實在的,C++中對NULL定義為0本喵覺得沒有道理。
??范圍for循環(huán)
范圍for我們也一直都在使用,這是C++11提供的語法糖,使用起來非常方便,它的底層就是迭代器,只是編譯器給自動替換了,本喵曾經(jīng)講解過,這里就不再詳細(xì)解釋了。
??STL中的一些變化
新容器:
紅色框中的是C++11增加的新容器,個人覺得只有unordered_map和unordered_set有用,其他很雞肋。
容器array對標(biāo)的是靜態(tài)數(shù)組,array也是一個靜態(tài)的,也就是在棧區(qū)上的,大小是通過一個非類型模板參數(shù)確定的。
- 唯一的優(yōu)勢:可以強制檢測越界。
具體的本喵在非類型模板參數(shù)一文中詳細(xì)講過。
容器forward_list是一個單鏈表,也很雞肋,因為絕大部分場景雙鏈表都可以滿足要求,而且更加方便,唯一使用到單鏈表的地方就是哈希桶中。
它的接口后面有after,表示在指定元素的后面進(jìn)行操作,比如插入到指定元素的后面。
- 楞要說單鏈表的優(yōu)勢,就是它比雙鏈表少一個節(jié)點指針變量。
至于unordered_map和unordered_set,這兩個容器的底層是哈希桶,雖然不能實現(xiàn)排序,但是可以降重。而且在查找時具有其他容器無法比擬的效率。這兩個容器是非常實用的,而且也是我們經(jīng)常使用的。
容器中的新方法:
- 使用列表構(gòu)造
這一點在前面本喵就講解過了,幾乎每個容器都增加了新的接口,使用std::initializer_list類型來構(gòu)造。
- 移動構(gòu)造和移動賦值
在下面本喵講解了右值引用就可以明白了。
- emplace_xxx插入接口或者右值引用版本的插入接口。
同樣在后面才能學(xué)習(xí)到。
??右值引用
什么是右值?什么是左值?
- 左值:一個表示數(shù)據(jù)的表達(dá)式,如變量名或者指針解引用。
- 特點:可以對左值取地址 + 可以對左值賦值。
上圖代碼中所示的變量都屬于左值,要牢記左值可以取地址這一個特性。
- 定義時const修飾符后的左值,不能給他賦值,但是可以取它的地址。
- 左值可以出現(xiàn)在賦值符合的左邊,也可以出現(xiàn)在賦值符合的右邊。
- 右值:也是一個表示數(shù)據(jù)的表達(dá)式。如:字面常量,表達(dá)式返回值,函數(shù)返回值,類型轉(zhuǎn)換時的臨時變量等等。
- 特點:右值不可以取地址,不可以賦值。
上面這些都是右值,要牢記右值特性–不能取地址不能賦值。
- 右值可以出現(xiàn)在賦值符號的右邊,但是不能出現(xiàn)出現(xiàn)在賦值符號的左邊。
什么是右值引用?
左值引用是給左值取別名,右值引用顯而易見就是給右值取別名。
- 右值引用使用兩個&符號。
上圖代碼中的rr1,rr2,rr3就是三個右值的別名,也就是右值引用。
??右值引用類型的左值屬性
- 右值是不能取地址的,但是給右值取別名后,會導(dǎo)致右值被存儲到特定位置,且可以取到該位置的地址。
- 對于內(nèi)置類型的右值,如字面常量,一旦右值引用以后,就會被存儲到特定的位置,并且可以取到該地址,而且還可以修改。
字面常量10原本是不可以被修改的,但是右值引用以后,在特定的位置開辟了變量來存放10,所以就可以被修改了。
表達(dá)式或者函數(shù)的返回值,會有一個臨時變量來存放返回值,我們知道這樣的臨時變量具有常性,也是右值。對于這種右值引用,編譯器會修改它的屬性,將常性修改,并且存儲在特定位置。
const類型的右值,即便開辟了變量存放該右值也是不可以被修改的,因為被const修飾了。
內(nèi)置類型的右值被稱為純右值。
- 對于自定義類型的右值,如容器的臨時變量,它確確實實會被銷毀,而不會被存放。
自定義類型的右值才能體現(xiàn)出右值存在的意義,后面本喵會詳細(xì)講解。
自定義類型的右值被稱為將亡值。
- 右值引用是右值的別名,它所指向的右值是不可以被修改的。
- 但是右值引用本身也是一種類型,并且它的屬性是左值,可以取地址,可以賦值。
左值引用總結(jié):
- 左值引用只能引用左值,不能引用右值
- const左值引用既可以引用左值,也可以引用右值
因為右值不可修改,只有加上const的左值引用去引用右值時才不會導(dǎo)致權(quán)限的放大,我們之前都是用const左值引用來引用右值的。
右值引用總結(jié):
- 右值引用只能引用右值,不能引用左值
- 右值引用可以引用move后的左值
左值經(jīng)過move以后就變成了右值,如:
int a = 10;
int&& rra = move(a);
??右值引用的場景和意義
先自己實現(xiàn)一個string,只有拷貝構(gòu)造函數(shù),賦值運算符重載函數(shù),析構(gòu)函數(shù),以及一個普通的構(gòu)造函數(shù)。無論是拷貝構(gòu)造還是賦值運算符重載,都會進(jìn)行深拷貝,采用現(xiàn)代寫法來實現(xiàn)。
左值引用的場景:
- 使用普通傳值調(diào)用,存在一次深拷貝。
- 使用傳拷貝調(diào)用時,不存在深拷貝,func函數(shù)直接使用main函數(shù)中的s1對象。
- 傳值返回時,存在一次深拷貝。
- 傳左值引用返回時,不存在深拷貝。
要知道深拷貝的代價是比較大的,深拷貝次數(shù)減少可以很大程度上提高代碼的效率。
但是左值引用存在短板:
前面我們在調(diào)用to_string函數(shù)的時候,形參就是左值引用,然后再返回,main函數(shù)傳過去的string對象一直存在。
此時需要拿到函數(shù)中的string對象,而且string對象是一個臨時變量,此時mian函數(shù)中拿到to_string中的string對象要進(jìn)行兩次深拷貝。
- 第一次深拷貝,to_string函數(shù)返回時,會將string對象放在一個臨時變量中,此時發(fā)生的深拷貝。
函數(shù)返回時,如果是內(nèi)置類型等幾個字節(jié)的變量,會將函數(shù)中的臨時變量放在寄存器中返回,如果是自定義類型所占空間比較大,就會放在臨時變量中壓棧到上一級棧幀中。
- 第二次深拷貝,main函數(shù)中,ret接收函數(shù)返回了的string對象時會再發(fā)生一次深拷貝。
但是編譯器會進(jìn)行優(yōu)化,將兩次深拷貝優(yōu)化成一次。
如上圖所示,可以看到發(fā)生了一次深拷貝,即使減少了一次,但是仍然存在代價。
- 我們現(xiàn)在想讓它一次拷貝都沒有。
我們現(xiàn)在的深拷貝感受不到代價比較大,試想如果深拷貝的是一個vector<vectot<…>>,此時就代價相當(dāng)大了。
- 函數(shù)調(diào)用使用傳左值引用返回。
但是報錯了,因為to_string函數(shù)中的string對象出了作用域會消失,此時傳引用返回就會發(fā)生類似野指針的問題。
所以現(xiàn)在要解決的就是讓局部的臨時對象出了作用域后不消失。
移動構(gòu)造
此時用右值引用就可以解決這個問題。
右值引用的價值之一:補齊臨時對象不能傳引用返回這個短板。
在string類中增加一個移動構(gòu)造函數(shù),如上圖所示。
- 移動構(gòu)造的形參是右值引用。
從to_string中返回的string對象是一個臨時變量,具有常性,也就是我們所說的右值。
- 用右值來構(gòu)造string對象時,會自定匹配移動構(gòu)造函數(shù)。
- 直接使用swap拿走將亡值。
返回的臨時變量是一個自定義類型的右值,也就是我們前面所說的將亡值。將亡值意味著馬上就要結(jié)束生命了,所以在移動構(gòu)造中,直接拿走將亡值,此時就不用發(fā)生深拷貝。
- 此時編譯器識別到了右值ret,然后匹配了移動構(gòu)造函數(shù)。
移動構(gòu)造減少了深拷貝的次數(shù),能夠更大程度上的提高效率,減少代價。
- to_string返回的是一個右值,用這個將亡值構(gòu)建新對象時,如果沒有移動構(gòu)造就會匹配構(gòu)造函數(shù),因為cons 左值引用可以引用右值。
- 如果有移動構(gòu)造就匹配移動構(gòu)造。
移動賦值
將賦值和創(chuàng)建string對象分開寫,此時編譯器就不會進(jìn)行優(yōu)化,在賦值的時候就會調(diào)用賦值運算符重載,而不是構(gòu)造函數(shù)。
可以看到,調(diào)用了一次移動構(gòu)造,一次深拷貝:
- to_string返回的是一個臨時變量,是一個右值,所以這里匹配移動構(gòu)造函數(shù)。
- 在賦值運算符重載中,現(xiàn)代寫法中會調(diào)用一次拷貝構(gòu)造的深拷貝,所以這兩會出現(xiàn)深拷貝。這里顯示的兩次深拷貝其實只有一次。
此時用一個右值進(jìn)行賦值仍然發(fā)生了深拷貝,可以采用同樣的思路,被賦值對象將右值直接拿過來使用。
- 將to_string返回的右值對象識別到以后,匹配移動賦值運算符重載函數(shù)。
- 在函數(shù)中使用swap直接拿走右值去使用。
此時只調(diào)用了一次移動構(gòu)造和一次移動賦值,都是直接使用右值對象,相比于深拷貝提高了很大的效率。
總結(jié):右值引用和左值引用減少拷貝的原理不太一樣。
- 左值引用是別名,直接在原本的對象上起作用。
- 右值引用是間接起作用,通過右值引用識別到右值,然后在移動構(gòu)造和移動賦值中進(jìn)行資源轉(zhuǎn)移。
使用移動構(gòu)造和移動賦值時,被轉(zhuǎn)移資源的對象必須是個將亡值,負(fù)責(zé)會被銷毀:
將左值對象s1通過move變成右值對象,用來構(gòu)建s2,匹配的是移動構(gòu)造函數(shù),通過調(diào)試窗口可以看到,當(dāng)s2被構(gòu)造好時,s1就被銷毀了,因為s1的資源被轉(zhuǎn)移了。
可以看到,C++11的STL標(biāo)準(zhǔn)庫中也提供了移動構(gòu)造和移動賦值函數(shù)。
右值引用的價值之二:插入右值時可以減少深度拷貝。
簡單實現(xiàn)一個鏈表,僅支持尾插。
向我們寫的list中插入我們實現(xiàn)的string匿名對象,如上圖所示。在插入過程中,多次調(diào)用了string的深拷貝。
- 每插入一個string對象時,就要new一個list中的節(jié)點,節(jié)點的構(gòu)造函數(shù)中對string對象進(jìn)行了深拷貝。
由于string對象都是匿名對象,都屬于右值,所以深拷貝是完全沒有必要的,最理想的狀況是用移動拷貝。
- 在list的尾插中增加右值引用類型的接口。
- 在節(jié)點的構(gòu)造函數(shù)中增加右值引用的構(gòu)造函數(shù)。
當(dāng)向list中插入string的匿名對象時,會匹配list的右值引用類型的尾插接口,在尾插接口中new一個新節(jié)點時,會匹配list_node的移動構(gòu)造函數(shù),又會使用string的移動構(gòu)造函數(shù)來初始化節(jié)點。
- 尾插接口以及節(jié)點的構(gòu)造函數(shù)中,需要使用move將右值引用的左值屬性改成右值,負(fù)責(zé)會匹配普通構(gòu)造函數(shù),而不是移動構(gòu)造函數(shù)。
因為右值引用類型本身是一個左值,所以需要使用move改變其屬性,使其始終保持右值屬性。
此時所有的插入都是使用的移動構(gòu)造,相比于深拷貝,效率提升了很多。
C++11為STL庫中添加了右值引用版本的尾插。
這里右值引用類的接口不能使用const修飾右值引用:
僅將尾插接口的形參改成const修飾的右值引用以后,在插入匿名對象時,調(diào)用的是string的深拷貝函數(shù),沒有匹配到移動構(gòu)造。
僅將節(jié)點構(gòu)移動構(gòu)造函數(shù)的右值引用用const修飾,同樣會導(dǎo)致無法匹配string的移動構(gòu)造函數(shù)。
- 右值引用會在特定位置開辟空間來存儲右值,所以右值引用本身的屬性是左值。
- 是左值就可以修改,尤其是在移動構(gòu)造函數(shù)中,會將右值引用里的右值轉(zhuǎn)移過來。
- 如果使用const修飾了右值引用,此時右值引用就不可以修改了,同樣就不可以進(jìn)行轉(zhuǎn)移了。
- 所以編譯器就去匹配深拷貝去了。
雖然右值不可以被修改,但是右值引用以后具有了左值屬性,才能被轉(zhuǎn)移,一旦被const修飾以后就無法轉(zhuǎn)移了。所以我們在使用右值引用的時候,不要使用const來修飾。
??完美轉(zhuǎn)發(fā)
??萬能引用
寫多個重載函數(shù),根據(jù)實參類型調(diào)用不同函數(shù)。
- 形參類型分別是左值引用,const左值引用,右值引用,const右值引用
上圖代碼中的perfectForward函數(shù)模板被叫做萬能引用模板,無論調(diào)用該函數(shù)時傳的是什么類型,它都能推演出來。
- 左值:調(diào)用模板函數(shù)時,實參是左值,推演出來的t就是左值引用。實參是const左值,推演出來的t就是const左值引用。
- 右值:調(diào)用模板函數(shù)時,實參是右值,推演出來的t就是右值引用。實參是const右值,推演出來的t就是const右值引用。
由于右值引用本身也是左值,所以需要通過move將其轉(zhuǎn)換成右值才能看到推演效果。
實參 | T | t |
---|---|---|
int a=10 | int | int&(左值引用) |
const int b=20 | const int | const int&(const左值引用) |
30 | int | int&&(右值引用) |
move(int c=40) | const int | const int&&(const 右值引用) |
在函數(shù)模板推演的過程中會發(fā)生引用折疊:模板參數(shù)T&&中的兩個&符號折疊成一個。
當(dāng)傳入的實參是左值時,就會發(fā)生引用折疊,是右值時就不會發(fā)生引用折疊。
- 無論傳的實參是什么,都不用改變模板參數(shù)T&&,編譯器都能夠自己推演。
這就是萬能引用,只需要一個模板就可以搞定,不需要分類去寫。
上面萬能模板中,雖然推演出來了各自實參類型,但是由于右值引用本身是左值屬性,所以需要使用move改變屬性后才能調(diào)用對應(yīng)的重載函數(shù)。
有沒有辦法不用move改變左值屬性,讓模板函數(shù)中的t保持它推演出來的類型。答案是有的,完美轉(zhuǎn)發(fā)就能夠保持形參的屬性不變。
- 完美轉(zhuǎn)發(fā):完美轉(zhuǎn)發(fā)在傳參的過程中保留對象原生類型屬性。
- 實參傳遞過來后,推演出的形參是什么類型就保持什么類型繼續(xù)使用。
完美轉(zhuǎn)發(fā)同樣是C++11提供的,它也是一個模板。
此時再使用萬能引用的時候,在函數(shù)模板中調(diào)用重載函數(shù)時只需要使用完美轉(zhuǎn)發(fā)就可以保持推演出來的屬性不變,右值引用仍然是右值,const右值引用也仍然是右值。
前面在向list中插入string匿名對象的時候,同樣面臨這個問題,當(dāng)時我們都是使用的move解決的,此時就可以使用完美轉(zhuǎn)發(fā)(forward)了。
可以看到,使用完美轉(zhuǎn)發(fā)同樣可以實現(xiàn)目的。
??新的類功能
在原來的C++類中,有6大默認(rèn)成員函數(shù):
構(gòu)造函數(shù),拷貝構(gòu)造函數(shù),賦值運算符重載,析構(gòu)函數(shù),取地址重載,const取地址重載。
最后重要的是前4個,后兩個用處不大。默認(rèn)成員函數(shù)就是我們不寫編譯器會生成一個默認(rèn)的,而且完全符號我們使用的需求。
在C++11中新增了兩個:移動構(gòu)造和移動賦值運算符重載。
這兩個成員函數(shù)在前面本喵已經(jīng)介紹過它是什么了,這里站在默認(rèn)成員函數(shù)的角度繼續(xù)談?wù)劇?/p>
滿足下列條件,編譯器會自定生成移動構(gòu)造函數(shù):
- 沒有自己顯示定義移動構(gòu)造函數(shù)
- 且沒有實現(xiàn)析構(gòu)函數(shù),拷貝構(gòu)造函數(shù),拷貝賦值重載中的任何一個。
此時編譯器會自定生成一個默認(rèn)的移動構(gòu)造函數(shù)。
- 默認(rèn)生成的移動構(gòu)造函數(shù),對于內(nèi)置類型會逐字節(jié)進(jìn)行拷貝。
- 對于自定義類型,如果實現(xiàn)了移動構(gòu)造就調(diào)用移動構(gòu)造,沒有實現(xiàn)就調(diào)用拷貝構(gòu)造。
創(chuàng)建一個類,屏蔽掉拷貝構(gòu)造,拷貝賦值,以及析構(gòu)函數(shù),成員變量有一個是我們自己實現(xiàn)的string,里面有移動構(gòu)造。
此時Person就自動生成了移動構(gòu)造函數(shù),并且調(diào)用了string中的移動構(gòu)造函數(shù)來構(gòu)造string對象。
- 將Person中的拷貝構(gòu)造,拷貝賦值,析構(gòu)函數(shù)任意放出一個來。
- 使用右值構(gòu)建string對象時,都會調(diào)用string的拷貝構(gòu)造函數(shù)。
滿足下列條件,編譯器會自動生成移動賦值重載函數(shù)
- 自己沒有顯示定義移動賦值重載函數(shù)。
- 且且沒有實現(xiàn)析構(gòu)函數(shù),拷貝構(gòu)造函數(shù),拷貝賦值重載中的任何一個。
此時編譯器會自動生成一個默認(rèn)移動賦值函數(shù)。
- 對于內(nèi)置類型會按字節(jié)拷貝。
- 對于自定義類型,如果實現(xiàn)了移動賦值就調(diào)用移動賦值,如果沒有實現(xiàn)就調(diào)用拷貝賦值。
和上面的移動構(gòu)造完全類型。
同樣將Person中的拷貝構(gòu)造,拷貝賦值,析構(gòu)函數(shù)屏蔽,給s4賦值右值對象。此時編譯器自動生成移動賦值,調(diào)用string的移動賦值函數(shù)。
同樣將上面的三個成員函數(shù)任意放出一個,編譯器都不會自動生成默認(rèn)移動賦值,而是會調(diào)用string的拷貝賦值函數(shù)。
- 編譯器默認(rèn)生成的移動賦值和移動構(gòu)造非常類型。
- 如果符合條件就生成,內(nèi)置內(nèi)心按字節(jié)處理,自定義類型調(diào)用自定義類型的移動賦值或者移動構(gòu)造,如果沒有的化就調(diào)用它們的拷貝賦值或者拷貝構(gòu)造。
- 如果不符合條件,就直接調(diào)用自定義類型的拷貝復(fù)制或者拷貝構(gòu)造。
??新的關(guān)鍵字
default:
這個default并不是switch中的default,而是C++11的新用法。
- 假設(shè)類中的某個默認(rèn)成員函數(shù)沒有自動生成,但是我們需要它,就可以用default,強制讓編譯器自動生成默認(rèn)函數(shù)。
將Person中的拷貝構(gòu)造,拷貝復(fù)制,析構(gòu)函數(shù)都顯示定義,此時就破壞了自動生成移動構(gòu)造的條件。
- 使用default強制生成默認(rèn)的移動構(gòu)造函數(shù),如上圖紅色框中所示。
從結(jié)果中可以看到,仍然調(diào)用了string中的移動構(gòu)造函數(shù),而不是調(diào)用的拷貝構(gòu)造。
- 說明Person中仍然生成了默認(rèn)的移動構(gòu)造函數(shù)。
delete
- 要求不生成默認(rèn)成員函數(shù)。
在Person類中不顯示定義拷貝構(gòu)造函數(shù),拷貝復(fù)制函數(shù),析構(gòu)函數(shù),此時符合自動生成默認(rèn)移動構(gòu)造的條件。
- 聲明移動構(gòu)造函數(shù),但是沒有定義。
此時在編譯的時候就會報錯,這是C++98中的方式,利用鏈接時找不到函數(shù)的定義報錯。
- C++11中,使用delete同樣可以實現(xiàn)不讓自動生成默認(rèn)成員函數(shù)。
同樣在編譯時報錯了。
編譯器會自動生成移動構(gòu)造函數(shù),但是此時使用了delete,編譯器就會報錯,告訴我們這里生成了移動構(gòu)造。
這是為了在編譯階段就報錯,而不是運行時再報錯,實話講,這個很雞肋。
final與override
這兩個關(guān)鍵字在繼承和多態(tài)部分本喵詳細(xì)講解過。
final
- 在繼承中,被final修飾的類叫做最終類,是無法繼承的。
- 在多態(tài)中,被final修飾的虛函數(shù)是無法進(jìn)行重寫的。
override文章來源:http://www.zghlxwxcb.cn/news/detail-456233.html
- 在多態(tài)中,用來檢查虛函數(shù)是否完成了重寫。
??總結(jié)
C++11中的很多東西雖然讓C++越來越不像C++,比如列表初始化等內(nèi)容,但是還是有一些非常有用的東西的,比如今天講到的右值引用,可以大大提高效率。文章來源地址http://www.zghlxwxcb.cn/news/detail-456233.html
到了這里,關(guān)于【C++學(xué)習(xí)】C++11——新特性 | 右值引用 | 完美轉(zhuǎn)發(fā)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!