13-拷貝控制

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ù)都加上引用限定符或都不加。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 13拷貝控制 13.1拷貝、賦值與銷毀 13.1.1拷貝構(gòu)造函數(shù) 拷貝構(gòu)造函數(shù)的第一個(gè)參數(shù)必須是引用類型。 使用拷...
    龜龜51閱讀 407評(píng)論 0 0
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,692評(píng)論 1 51
  • 前言 人生苦多,快來(lái) Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,761評(píng)論 9 118
  • 目錄丨 上一章 引 2015年12月31日 跨年夜 上海 外灘 和平飯店 每年的跨年夜,魔都的外灘都是人頭竄...
    芊澤巖閱讀 570評(píng)論 2 8
  • 是誰(shuí)
    半夏花開(kāi)半夏閱讀 161評(píng)論 0 0

友情鏈接更多精彩內(nèi)容