21:知識點:判斷一個類是否需要拷貝控制函數(shù)成員,首先判斷其是否需要自定義版本的析構(gòu)函數(shù),如果需要,則拷貝控制成員函數(shù)都需要。由于這兩個類中的指針為智能指針,可以自動控制內(nèi)存的釋放,所以使用類的合成析構(gòu)函數(shù)即可。另外類默認(rèn)的拷貝控制成員對于智能指針的拷貝也不需要自定義版本來修改,所以全部定義為 =default 即可
22:知識點1:管理類外資源的類必須定義拷貝控制成員
知識點2:為了定義拷貝控制成員,我們可以定義拷貝操作,使得類的行為看起來像是一個值或者一個指針
知識點3:類的行為像一個值,拷貝發(fā)生時,副本和原對象是完全獨立的,改變副本不會對原對象產(chǎn)生影響
知識點4:類的行為像一個指針,拷貝發(fā)生時,副本和原對象共用底層數(shù)據(jù),改變副本也會改變原對象
知識點5:標(biāo)準(zhǔn)庫容器和string類就是像值的類,shared_ptr類就是像指針的類,IO類和unique_ptr不允許拷貝和賦值,所以都不是
class Hasptr{public: Hasptr();//默認(rèn)構(gòu)造函數(shù) //拷貝構(gòu)造函數(shù),完成string 指針指向內(nèi)容的拷貝和i值的拷貝 Hasptr(const Hasptr& p):ps(new string(*p.ps)),i(p.i){} //拷貝賦值運算符 Hasptr& Operator= (const Hasptr& p) { auto new_ps = new string(p.ps); delete ps; ps = new_ps; return *this; } //析構(gòu)函數(shù) ~Hasptr(){delete ps;}PRivate: string *ps; int i;};23:知識點1:類值版本,類的構(gòu)造函數(shù)需要可能需要動態(tài)分配其成員的副本
知識點2:類值版本,類的拷貝賦值運算符相當(dāng)于結(jié)合了構(gòu)造函數(shù)和析構(gòu)函數(shù)的操作,首先銷毀左側(cè)運算對象的資源,再從右側(cè)運算符對象拷貝資源,注意順序
知識點3:由于有上述的順序存在,所以我們必須保證這樣的拷貝賦值運算符是正確的:首先將右側(cè)運算對象拷貝到一個臨時的對象中,再銷毀左側(cè)的運算對象的現(xiàn)有成員,之后將臨時對象中的數(shù)據(jù)成員拷貝至左側(cè)對象中(防范自賦值的情況發(fā)生—首先就銷毀了自身的成員,再進行拷貝自身則會訪問到已經(jīng)釋放的內(nèi)存中)
見22題,編寫時忘了一個析構(gòu)函數(shù),ps在構(gòu)造函數(shù)中是動態(tài)分配的內(nèi)存,所以需要進行delete
24:未定義析構(gòu)函數(shù),ps在使用結(jié)束后不會被合成版本的析構(gòu)函數(shù)釋放,造成內(nèi)存泄漏。未定義拷貝構(gòu)造函數(shù),使用自定義版本的拷貝構(gòu)造函數(shù),對于ps的拷貝就會是指針本身的拷貝。
25:動態(tài)分配的內(nèi)存由shared_ptr管理,析構(gòu)函數(shù)之后會自動判斷進行釋放,所以不需要自定義版本的析構(gòu)函數(shù)。
26:知識點1:定義行為像指針的類,在不想使用shared_ptr的情況下我們可以使用引用計數(shù)來確定是否釋放內(nèi)存
知識點2:每個構(gòu)造函數(shù)(拷貝構(gòu)造函數(shù)除外)都創(chuàng)建一個引用計數(shù),記錄對象的共享狀態(tài),第一次被新建時,計數(shù)為1
知識點3:析構(gòu)函數(shù)遞減引用計數(shù),拷貝賦值運算符遞增右側(cè)對象的引用計數(shù),遞減左側(cè)的,當(dāng)左側(cè)的引用計數(shù)為0時,拷貝賦值運算符就必須銷毀狀態(tài)
知識點4:計數(shù)器不能直接作為類對象的成員,否則在拷貝中,會出現(xiàn)歧義,我們可以將計數(shù)器保存在動態(tài)內(nèi)存中,只定義一個指向計數(shù)器的指針,這樣拷貝或者賦值時,我們拷貝該指針,副本和原對象指向同樣的計數(shù)器
class Hasptr1{public: //構(gòu)造函數(shù),初始化相關(guān)成員 Hasptr1(const string& s = string()):ps(new string(s)),i(0),use(new size_t(1)){} //拷貝構(gòu)造函數(shù),將引用計數(shù)也拷貝過來,并且遞增引用計數(shù) Hasptr1(const Hasptr1& p):ps(p.ps),i(p.i),use(p.use){++*use;} //拷貝賦值運算符 Hasptr1& operator= (const Hasptr1& p1) { ++*p1.use;//首先遞增右側(cè)運算符對象的引用計數(shù) if (--*use == 0)//遞減本對象的引用計數(shù),若沒有其他用戶,則釋放本對象的成員 { delete ps; delete use; } ps = p1.ps;//進行拷貝 use = p1.use; i = p1.i; return *this; } //析構(gòu)函數(shù) ~Hasptr1() { if (*use == 0)//引用計數(shù)變?yōu)?,說明已經(jīng)沒有對象再需要這塊內(nèi)存,進行釋放內(nèi)存操作 { delete ps; delete use; } }private: //定義為指針,是我們想將該string對象保存在動態(tài)內(nèi)存中 string *ps; size_t *use;//將計數(shù)器的引用保存 int i;};28:(a)類似于27題 (b)只有一個指針成員,參照27題
29:知識點1:如果一個類定義了自己的swap,那么算法將利用類自己的版本(重排順序等算法)
知識點2:自定義版本的swap存在的必要性:我們不希望進行新的內(nèi)存分配,只希望將其指針進行拷貝賦值(交換的本質(zhì)),省去不必要的內(nèi)存分配,將函數(shù)定義為friend,以便訪問private成員
知識點3:相對于拷貝控制成員,swap并不是不要的,但是對于那些分配了資源的類,定義swap可能是一種很重要的優(yōu)化手段
知識點4:swap函數(shù)自定義版本與std中版本的重合問題:對于swap函數(shù),其調(diào)用應(yīng)該都是不加限定的,若加std::swap則調(diào)用的是標(biāo)準(zhǔn)庫的版本,而標(biāo)準(zhǔn)庫的版本在一定程度上是為了那些內(nèi)置類型沒有自定義版本的swap而準(zhǔn)備的,若一個類有其自定義版本的swap函數(shù),則我們就不應(yīng)該使用std版本的。所以我們只要在前加上using std::swap聲明,即可,在使用中,若有類特定的swap,其匹配程度則會優(yōu)于std中的版本(616頁有詳解)
知識點5:在賦值運算符中使用swap,以傳值的方式傳入新對象,再進行拷貝賦值,在一定程度上會比較安全
見知識點4,因為其調(diào)用到最后使用的是std中的swap,不存在循環(huán)
30:
class Hasptr{ friend void swap(Hasptr&,Hasptr&);public: Hasptr();//默認(rèn)構(gòu)造函數(shù) //拷貝構(gòu)造函數(shù),完成string 指針指向內(nèi)容的拷貝和i值的拷貝 Hasptr(const Hasptr& p):ps(new string(*p.ps)),i(p.i){} //拷貝賦值運算符 Hasptr& operator= (const Hasptr& p) { auto new_ps = new string(*p.ps); delete ps; ps = new_ps; return *this; } //析構(gòu)函數(shù) ~Hasptr(){delete ps;}private: string *ps; int i;};inline void swap(Hasptr& a,Hasptr& b){ using std::swap; swap(a.ps,b.ps); std::swap(a.i,b.i); cout<<"123";}
|
新聞熱點
疑難解答
圖片精選