13.1 拷貝靴迫,賦值與銷毀
以上這些操作玉锌,必須明白定義與不定義會對類的操作產生何種影響主守,變編譯器定義的合成版本未必符合類設計的初衷榄融。
13.1.1 拷貝構造函數
如果一個構造函數的第一個參數是同類型的引用,且任何其他參數都有默認值救湖,則此構造函數為拷貝構造函數。
必須是引用類型參數力九,因為在調用非引用參數的函數時,會拷貝實參舒萎,而拷貝實參又需要調用拷貝構造函數,那么會無休止的調用下去咆贬。
Foo(const Foo&);
合成拷貝構造函數會將其參數的成員(非static)逐個拷貝到正在創(chuàng)建的對象中掏缎。
直接初始化:使用普通的函數匹配,選擇參數最匹配的構造函數酌儒。
拷貝初始化:將對象或者可以轉換為相同類型的對象拷貝到正在創(chuàng)建的對象中酪夷。
1榴啸,使用=運算符定義變量
2,將對象作為實參傳遞給一個非引用類型
3狂鞋,從非引用類型返回類型的函數里返回一個對象
4要销,使用初始值列表初始化一個數組中的元素或一個聚類中的成員
13.1.2 拷貝賦值運算符
Foo& operator=(const Foo&);
如果運算符是一個成員函數,其左側對象就綁定到隱式的this參數脐供。
合成拷貝賦值運算符:將右側運算對象的每個成員(非static)賦予左側運算對象的對應成員
13.1.3 析構函數
~Foo();
在一個構造函數中政己,成員的初始化是在函數體執(zhí)行之前完成的歇由,且按照在類中出現的順序進行的果港;而在析構函數中谢谦,首先執(zhí)行函數體猩谊,然后銷毀成員,成員按初始化順序逆序銷毀。
當一個對象被銷毀時會自動調用其析構函數。當指向一個對象的引用會指針離開作用域是焰情,析構函數不會執(zhí)行。
合成析構函數:空的函數體
13.1.4 三/五法則
如果一個類需要自定義析構函數(一般是銷毀動態(tài)分配的內存),則可能也需要拷貝構造函數和拷貝賦值運算符。
需要拷貝操作的類也需要賦值操作,反之亦然谈为,但未必需要析構函數耘成。
13.1.5 使用=default
使用=default修飾拷貝控制成員榔昔,編譯器將生成相應成員的合成版本。=default在類內則隱式的聲明為內聯瘪菌。
Foo& operator=(const Foo&) = default;
Foo(const Foo&) =default撒会;
只能用來修飾具有合成版本的成員函數。
13.1.6 阻止拷貝
將拷貝構造函數和拷貝賦值運算符定義為刪除的函數來阻止拷貝师妙,即诵肛,雖然聲明了,但是不可以使用它們默穴。
Foo(const Foo&) = delete;//阻止拷貝
Foo& operator=(const Foo&) = delete;//阻止賦值
=delete必須出現在第一次聲明的時候怔檩;可以對任何函數指定=delete
對于刪除了析構函數的類型,不可以定義此類型的變量或成員蓄诽,但可以動態(tài)分配這種類型薛训,但是無法釋放它們。
合成的拷貝控制成員可能是刪除的
如果類有數據成員不能被默認構造仑氛,拷貝乙埃,賦值闸英,復制或銷毀(delete或者private修飾),則此類對應的合成成員函數被定義為刪除的介袜。(引用成員或無法默認構造的const成員)
通過聲明但不定義private的拷貝構造函數或拷貝賦值運算符甫何,試圖拷貝或賦值的操作在編譯階段被標記為錯誤;成員函數或友元函數中的拷貝或賦值會導致鏈接錯誤遇伞。(舊的方法)
13.2 拷貝控制和資源管理
管理類外的類必須定義拷貝控制成員辙喂。
13.2.1 行為像值的類
對于類管理的資源,每個對象都有一份拷貝鸠珠。
賦值操作會銷毀左側運算對象的資源巍耗,之后從右側運算對象拷貝數據,必須確保自賦值是異常安全的(可先將右側運算對象的數據拷貝到零時對象中)跳芳。
13.2.2 行為像指針的類
多個此類的對象共享同一份數據(使用智能指針或引用計數管理)
13.3 交換操作
void swap(Foo &lhs, Foo &rhs){
using std::swap;//若沒有類型自定義的swap版本芍锦,則調用std的版本
swap(lhs.h, rhs.h);//類型自定義的版本
}
拷貝并交換技術
Foo& Foo::operator=(Foo rhs){
swap(*this, rhs);
return *this;
}
參數并不是引用竹勉,在函數執(zhí)行完后會釋放飞盆。可自動處理自賦值的情況次乓,且是異常安全的吓歇。
13.6 對象移動
當一個對象拷貝后就立即銷毀,此時使用移動操作可以提高性能票腰。
標準庫容器城看,string,shared_ptr類支持移動和拷貝杏慰,IO類和unique_ptr類只支持移動测柠。
13.6.1 右值引用
即,必須綁定到右值的引用缘滥。只能綁定到將要銷毀的對象轰胁,故可以將右值引用的資源移動到另一個對象中。
一個左值表達式表示一個對象的身份朝扼,而右值表達式表示的是對象的值赃阀。
int &&r = i*42;
返回左值引用的函數,連同賦值擎颖,下標榛斯,解引用和前置遞增遞減運算符,都返回左值搂捧;
返回非引用類型的函數驮俗,連同算數,關系允跑,位以及后置遞增遞減運算符王凑,都生成右值提佣,可以用const的左值引用和右值引用綁定。
左值有持久的狀態(tài)荤崇,右值要么是字面值常量拌屏,要么是表達式求值過程中創(chuàng)建的臨時變量。
右值意味著:該對象將被銷毀术荤;該對象沒有其他用戶倚喂。故使用右值引用的代碼可以自由的接管引用的對象的資源。
int &&rr1 = 42;
int &&rr2 = rr1;//錯誤:rr1是右值引用類型的變量瓣戚,是左值
#include <utility>
int &&rr3 ?=std::move(rr1);//move函數獲得綁定到左值上的右值引用端圈。
move意味著希望像處理右值一樣處理一個左值,即子库,除了對rr1賦值和銷毀外舱权,代碼不會再使用它。
13.6.2 移動構造函數和移動賦值運算符
從給定對象“竊取”而不是拷貝資源仑嗅。
移動拷貝構造函數第一個參數是該類類型的右值引用宴倍,其他的參數必須都具有默認實參。
StrVec::StrVec(StrVec &&s) noexcept:e(s.e), f(s.f){s.e = s.f = nullptr;}
1仓技,移動操作不應該拋出異常鸵贬,noexcept通知標準庫移動操作是安全的的,無需標準庫做額外的操作
2脖捻,成員初始化器中接管s中的資源
3阔逼,函數體中使對s進行析構是安全的
noexcept在聲明和定義中都需要指定。
StrVec &StrVec::operator=(StrVec &&rhs) noexcept{}
在移動操作之后地沮,移后源對象必須保持有效的嗜浮,可析構的狀態(tài),但用戶不可對其值進行任何假設摩疑。
當一個類沒有定義任何自己版本的拷貝控制成員危融,且類的每個非static數據成員都可以移動時,編譯器才會合成移動構造函數或移動賦值運算符未荒。
可以移動:內置類型专挪,類類型定義了相應的移動操作。
移動操作不會隱式的定義為刪除的函數片排,但顯式定義=default的移動操作寨腔,且編譯器不能移動所有成員,則編譯器將移動操作定義為刪除的函數率寡。
如果類定義了一個移動構造函數或一個移動賦值運算符迫卢,則該類的合成拷貝構造函數和拷貝賦值運算符會被定義為刪除的。
如果一個類既有移動構造函數冶共,又有拷貝構造函數乾蛤,則使用普通的函數匹配規(guī)則來選擇調用每界。如果沒有移動構造函數或移動賦值運算符,右值會被拷貝家卖。
移動迭代器
移動迭代器的解引用運算符生成一個右值引用眨层,make_move_iterator(origin_iterator);函數將普通的迭代器轉換為一個移動迭代器,會調用相應的西東構造函數或移動賦值運算符操作上荡。
13.6.3 右值引用和成員函數
成員函數提供移動版本:
void push_back(const X&);//拷貝
void push_back(X&&);//移動
引用限定符
Foo &operator=(const Foo&) &;//只能向可修改的左值賦值
引用限定符可以是&和&&趴樱,分別指出this可以指向一個左值或右值,只能用于非static的成員函數酪捡,且必須同時出現在聲明和定義中叁征。
一個函數可以同時用const和引用限定,但引用限定符必須跟隨在const之后逛薇。
引用限定符可以區(qū)分重載版本捺疼,并且可以和const綜合起來區(qū)分。
當定義多個具有相同名字和相同參數列表的成員函數時永罚,必須所有函數都加上引用限定符或都不加啤呼。