13.1 拷貝,賦值與銷毀
以上這些操作,必須明白定義與不定義會(huì)對(duì)類的操作產(chǎn)生何種影響,變編譯器定義的合成版本未必符合類設(shè)計(jì)的初衷。
13.1.1 拷貝構(gòu)造函數(shù)
如果一個(gè)構(gòu)造函數(shù)的第一個(gè)參數(shù)是同類型的引用,且任何其他參數(shù)都有默認(rèn)值,則此構(gòu)造函數(shù)為拷貝構(gòu)造函數(shù)。
必須是引用類型參數(shù),因?yàn)樵谡{(diào)用非引用參數(shù)的函數(shù)時(shí),會(huì)拷貝實(shí)參,而拷貝實(shí)參又需要調(diào)用拷貝構(gòu)造函數(shù),那么會(huì)無(wú)休止的調(diào)用下去。
Foo(const Foo&);
合成拷貝構(gòu)造函數(shù)會(huì)將其參數(shù)的成員(非static)逐個(gè)拷貝到正在創(chuàng)建的對(duì)象中。
直接初始化:使用普通的函數(shù)匹配,選擇參數(shù)最匹配的構(gòu)造函數(shù)。
拷貝初始化:將對(duì)象或者可以轉(zhuǎn)換為相同類型的對(duì)象拷貝到正在創(chuàng)建的對(duì)象中。
1,使用=運(yùn)算符定義變量
2,將對(duì)象作為實(shí)參傳遞給一個(gè)非引用類型
3,從非引用類型返回類型的函數(shù)里返回一個(gè)對(duì)象
4,使用初始值列表初始化一個(gè)數(shù)組中的元素或一個(gè)聚類中的成員
13.1.2 拷貝賦值運(yùn)算符
Foo& operator=(const Foo&);
如果運(yùn)算符是一個(gè)成員函數(shù),其左側(cè)對(duì)象就綁定到隱式的this參數(shù)。
合成拷貝賦值運(yùn)算符:將右側(cè)運(yùn)算對(duì)象的每個(gè)成員(非static)賦予左側(cè)運(yùn)算對(duì)象的對(duì)應(yīng)成員
13.1.3 析構(gòu)函數(shù)
~Foo();
在一個(gè)構(gòu)造函數(shù)中,成員的初始化是在函數(shù)體執(zhí)行之前完成的,且按照在類中出現(xiàn)的順序進(jìn)行的;而在析構(gòu)函數(shù)中,首先執(zhí)行函數(shù)體,然后銷毀成員,成員按初始化順序逆序銷毀。
當(dāng)一個(gè)對(duì)象被銷毀時(shí)會(huì)自動(dòng)調(diào)用其析構(gòu)函數(shù)。當(dāng)指向一個(gè)對(duì)象的引用會(huì)指針離開(kāi)作用域是,析構(gòu)函數(shù)不會(huì)執(zhí)行。
合成析構(gòu)函數(shù):空的函數(shù)體
13.1.4 三/五法則
如果一個(gè)類需要自定義析構(gòu)函數(shù)(一般是銷毀動(dòng)態(tài)分配的內(nèi)存),則可能也需要拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符。
需要拷貝操作的類也需要賦值操作,反之亦然,但未必需要析構(gòu)函數(shù)。
13.1.5 使用=default
使用=default修飾拷貝控制成員,編譯器將生成相應(yīng)成員的合成版本。=default在類內(nèi)則隱式的聲明為內(nèi)聯(lián)。
Foo& operator=(const Foo&) = default;
Foo(const Foo&) =default;
只能用來(lái)修飾具有合成版本的成員函數(shù)。
13.1.6 阻止拷貝
將拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符定義為刪除的函數(shù)來(lái)阻止拷貝,即,雖然聲明了,但是不可以使用它們。
Foo(const Foo&) = delete;//阻止拷貝
Foo& operator=(const Foo&) = delete;//阻止賦值
=delete必須出現(xiàn)在第一次聲明的時(shí)候;可以對(duì)任何函數(shù)指定=delete
對(duì)于刪除了析構(gòu)函數(shù)的類型,不可以定義此類型的變量或成員,但可以動(dòng)態(tài)分配這種類型,但是無(wú)法釋放它們。
合成的拷貝控制成員可能是刪除的
如果類有數(shù)據(jù)成員不能被默認(rèn)構(gòu)造,拷貝,賦值,復(fù)制或銷毀(delete或者private修飾),則此類對(duì)應(yīng)的合成成員函數(shù)被定義為刪除的。(引用成員或無(wú)法默認(rèn)構(gòu)造的const成員)
通過(guò)聲明但不定義private的拷貝構(gòu)造函數(shù)或拷貝賦值運(yùn)算符,試圖拷貝或賦值的操作在編譯階段被標(biāo)記為錯(cuò)誤;成員函數(shù)或友元函數(shù)中的拷貝或賦值會(huì)導(dǎo)致鏈接錯(cuò)誤。(舊的方法)
13.2 拷貝控制和資源管理
管理類外的類必須定義拷貝控制成員。
13.2.1 行為像值的類
對(duì)于類管理的資源,每個(gè)對(duì)象都有一份拷貝。
賦值操作會(huì)銷毀左側(cè)運(yùn)算對(duì)象的資源,之后從右側(cè)運(yùn)算對(duì)象拷貝數(shù)據(jù),必須確保自賦值是異常安全的(可先將右側(cè)運(yùn)算對(duì)象的數(shù)據(jù)拷貝到零時(shí)對(duì)象中)。
13.2.2 行為像指針的類
多個(gè)此類的對(duì)象共享同一份數(shù)據(jù)(使用智能指針或引用計(jì)數(shù)管理)
13.3 交換操作
void swap(Foo &lhs, Foo &rhs){
using std::swap;//若沒(méi)有類型自定義的swap版本,則調(diào)用std的版本
swap(lhs.h, rhs.h);//類型自定義的版本
}
拷貝并交換技術(shù)
Foo& Foo::operator=(Foo rhs){
swap(*this, rhs);
return *this;
}
參數(shù)并不是引用,在函數(shù)執(zhí)行完后會(huì)釋放。可自動(dòng)處理自賦值的情況,且是異常安全的。
13.6 對(duì)象移動(dòng)
當(dāng)一個(gè)對(duì)象拷貝后就立即銷毀,此時(shí)使用移動(dòng)操作可以提高性能。
標(biāo)準(zhǔn)庫(kù)容器,string,shared_ptr類支持移動(dòng)和拷貝,IO類和unique_ptr類只支持移動(dòng)。
13.6.1 右值引用
即,必須綁定到右值的引用。只能綁定到將要銷毀的對(duì)象,故可以將右值引用的資源移動(dòng)到另一個(gè)對(duì)象中。
一個(gè)左值表達(dá)式表示一個(gè)對(duì)象的身份,而右值表達(dá)式表示的是對(duì)象的值。
int &&r = i*42;
返回左值引用的函數(shù),連同賦值,下標(biāo),解引用和前置遞增遞減運(yùn)算符,都返回左值;
返回非引用類型的函數(shù),連同算數(shù),關(guān)系,位以及后置遞增遞減運(yùn)算符,都生成右值,可以用const的左值引用和右值引用綁定。
左值有持久的狀態(tài),右值要么是字面值常量,要么是表達(dá)式求值過(guò)程中創(chuàng)建的臨時(shí)變量。
右值意味著:該對(duì)象將被銷毀;該對(duì)象沒(méi)有其他用戶。故使用右值引用的代碼可以自由的接管引用的對(duì)象的資源。
int &&rr1 = 42;
int &&rr2 = rr1;//錯(cuò)誤:rr1是右值引用類型的變量,是左值
#include <utility>
int &&rr3 ?=std::move(rr1);//move函數(shù)獲得綁定到左值上的右值引用。
move意味著希望像處理右值一樣處理一個(gè)左值,即,除了對(duì)rr1賦值和銷毀外,代碼不會(huì)再使用它。
13.6.2 移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符
從給定對(duì)象“竊取”而不是拷貝資源。
移動(dòng)拷貝構(gòu)造函數(shù)第一個(gè)參數(shù)是該類類型的右值引用,其他的參數(shù)必須都具有默認(rèn)實(shí)參。
StrVec::StrVec(StrVec &&s) noexcept:e(s.e), f(s.f){s.e = s.f = nullptr;}
1,移動(dòng)操作不應(yīng)該拋出異常,noexcept通知標(biāo)準(zhǔn)庫(kù)移動(dòng)操作是安全的的,無(wú)需標(biāo)準(zhǔn)庫(kù)做額外的操作
2,成員初始化器中接管s中的資源
3,函數(shù)體中使對(duì)s進(jìn)行析構(gòu)是安全的
noexcept在聲明和定義中都需要指定。
StrVec &StrVec::operator=(StrVec &&rhs) noexcept{}
在移動(dòng)操作之后,移后源對(duì)象必須保持有效的,可析構(gòu)的狀態(tài),但用戶不可對(duì)其值進(jìn)行任何假設(shè)。
當(dāng)一個(gè)類沒(méi)有定義任何自己版本的拷貝控制成員,且類的每個(gè)非static數(shù)據(jù)成員都可以移動(dòng)時(shí),編譯器才會(huì)合成移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值運(yùn)算符。
可以移動(dòng):內(nèi)置類型,類類型定義了相應(yīng)的移動(dòng)操作。
移動(dòng)操作不會(huì)隱式的定義為刪除的函數(shù),但顯式定義=default的移動(dòng)操作,且編譯器不能移動(dòng)所有成員,則編譯器將移動(dòng)操作定義為刪除的函數(shù)。
如果類定義了一個(gè)移動(dòng)構(gòu)造函數(shù)或一個(gè)移動(dòng)賦值運(yùn)算符,則該類的合成拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符會(huì)被定義為刪除的。
如果一個(gè)類既有移動(dòng)構(gòu)造函數(shù),又有拷貝構(gòu)造函數(shù),則使用普通的函數(shù)匹配規(guī)則來(lái)選擇調(diào)用。如果沒(méi)有移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值運(yùn)算符,右值會(huì)被拷貝。
移動(dòng)迭代器
移動(dòng)迭代器的解引用運(yùn)算符生成一個(gè)右值引用,make_move_iterator(origin_iterator);函數(shù)將普通的迭代器轉(zhuǎn)換為一個(gè)移動(dòng)迭代器,會(huì)調(diào)用相應(yīng)的西東構(gòu)造函數(shù)或移動(dòng)賦值運(yùn)算符操作。
13.6.3 右值引用和成員函數(shù)
成員函數(shù)提供移動(dòng)版本:
void push_back(const X&);//拷貝
void push_back(X&&);//移動(dòng)
引用限定符
Foo &operator=(const Foo&) &;//只能向可修改的左值賦值
引用限定符可以是&和&&,分別指出this可以指向一個(gè)左值或右值,只能用于非static的成員函數(shù),且必須同時(shí)出現(xiàn)在聲明和定義中。
一個(gè)函數(shù)可以同時(shí)用const和引用限定,但引用限定符必須跟隨在const之后。
引用限定符可以區(qū)分重載版本,并且可以和const綜合起來(lái)區(qū)分。
當(dāng)定義多個(gè)具有相同名字和相同參數(shù)列表的成員函數(shù)時(shí),必須所有函數(shù)都加上引用限定符或都不加。