第 13 章 拷貝控制

  • 當定義一個類時,我們顯式地或隱式地指定在此類型的對象拷貝脸甘,移動恳啥,賦值和銷毀時做什么
  • 一個類通過五種特殊的成員函數(shù)來控制這些操作,包括:拷貝構造函數(shù)丹诀,拷貝賦值運算符钝的,移動構造函數(shù)翁垂,移動賦值運算符析構函數(shù)
  • 拷貝和移動構造函數(shù)定義了當用同類型的另一個對象初始化本對象時做什么∠踝拷貝和移動賦值運算符定義了將一個對象賦予同類型的另一個對象時做什么沿猜。析構函數(shù)定義了當此類型對象銷毀時做什么
  • 我們稱這些操作為拷貝控制操作

13.1 拷貝、賦值與銷毀

  • 以最基本操作 拷貝構造函數(shù)碗脊,拷貝賦值運算符析構函數(shù) 作為開始啼肩。移動操作在 13.6 節(jié)講述

13.1.1 拷貝構造函數(shù)

  • 如果一個構造函數(shù)第一個參數(shù)是自身類類型的引用,且任何額外參數(shù)都有默認值衙伶,則稱此構造函數(shù)是拷貝構造函數(shù)
拷貝初始化
  • 當使用直接初始化時祈坠,我們實際上時要求編譯器使用普通的函數(shù)匹配來選擇與我們提供的參數(shù)最匹配的構造函數(shù)
  • 當我們使用拷貝初始化時,我們要求編譯器將右側運算對象拷貝到正在創(chuàng)建的對象中矢劲,如果需要的話還要進行類型轉換
string dots(10, '.');    // 直接初始化
string s(dots);         // 直接初始化
string s2 = dots;       // 拷貝初始化
string null_book = "9-999-99999-9";     // 拷貝初始化
string nines = string(100, '9');        // 拷貝初始化
參數(shù)和返回值
  • 在函數(shù)調用過程中赦拘,具有非引用類型的參數(shù)要進行拷貝初始化
  • 當一個函數(shù)具有非引用的返回類型時,返回值會被用來初始化調用方的結果

13.1.2 拷貝賦值運算符

  • 與類控制其對象如何初始化一樣芬沉,類也可以控制其對象如何賦值
Sales_data trans, accum;
trans = accum;   // 使用 Sales_data 的拷貝賦值運算符
  • 與拷貝構造函數(shù)一樣躺同,如果類未定義自己的拷貝賦值運算符,編譯器會為它合成一個
重載賦值運算符
  • 重載運算符本質上是函數(shù)丸逸,其名字由 operator 關鍵字后接表示要定義的運算符的符號組成蹋艺。因此,賦值運算符就是一個名為 operator= 的函數(shù)黄刚。類似于任何其他函數(shù)捎谨,運算符函數(shù)也有一個返回類型和一個參數(shù)列表
  • 賦值運算符通常應該返回一個指向其左側運算對象的引用
class Foo{
    public:
        Foo& operator=(const Foo&);  // 賦值運算符
        // ....
};
A& operator= (const A& a){ //拷貝賦值運算符
    val = a.val;
    return *this;
}
  • 與處理拷貝構造函數(shù)一樣,如果一個類未定義自己的拷貝賦值運算符隘击,編譯器會為它生成一個合成拷貝賦值運算符

13.1.3 析構函數(shù)

  • 析構函數(shù)執(zhí)行與構造函數(shù)相反的操作:構造函數(shù)初始化對象的非 static 數(shù)據(jù)成員侍芝,還可能做一些其他工作;析構函數(shù)釋放對象使用的資源埋同,并銷毀對象的非 static 數(shù)據(jù)成員
  • 析構函數(shù)是類的一個成員函數(shù),名字由波浪號接類名構成棵红。它沒有返回值也不接受參數(shù)
class Foo{
    public:
        ~Foo();  // 析構函數(shù)
        // ...
};
  • 由于析構函數(shù)不接受參數(shù)凶赁,因此它不能被重載。對于一個給定類逆甜,只會有唯一一個析構函數(shù)
  • 無論何時一個對象被銷毀虱肄,就會自動調用其析構函數(shù)
    1, 變量在離開其作用域時被銷毀
    2, 當一個對象被銷毀時,其成員被銷毀
    3, 容器被銷毀時交煞,其元素被銷毀
    4, 對于動態(tài)分配的對象咏窿,當對指向它的指針應用 delete 運算符時被銷毀
    5, 對于臨時對象,當創(chuàng)建它的完整表達式結束時被銷毀
  • 析構函數(shù)體自身并不直接銷毀成員素征。成員是在析構函數(shù)體之后隱含的析構階段中被銷毀的

13.1.4 三/五法則

  • 如果一個類需要一個析構函數(shù)集嵌,我們幾乎可以肯定它也需要一個拷貝構造函數(shù)和一個拷貝賦值運算符
  • 如果一個類需要一個拷貝構造函數(shù)萝挤,幾乎可以肯定它也需要一個拷貝賦值運算符。反之亦然 - 如果一個類需要一個拷貝賦值運算符根欧,幾乎可以肯定它也需要一個拷貝構造函數(shù)怜珍。然而,無論是需要拷貝構造函數(shù)還是需要拷貝賦值運算符都不必然意味著也需要析構函數(shù)

13.1.5 使用 =default

  • 我們可以通過將拷貝控制成員定義為 =default 來顯式地要求編譯器生成合成的版本凤粗。合成的版本就是默認版本

13.1.5 阻止拷貝

  • 大多數(shù)類應該定義默認構造函數(shù)酥泛、拷貝構造函數(shù)和拷貝賦值運算符,無論是隱式地還是顯式地
  • 雖然大多數(shù)類應該定義拷貝構造函數(shù)和拷貝賦值運算符嫌拣,但對某些類來說柔袁,這些操作沒有合理的意義。在此情況下异逐,定義類時必須采用某種機制阻止拷貝或賦值
定義刪除的函數(shù)
  • 在新標準下瘦馍,我們可以將拷貝構造函數(shù)和拷貝賦值運算符定義為刪除的函數(shù)來阻止拷貝。
  • 刪除的函數(shù)是這樣一種函數(shù): 我們雖然聲明了它們应役,但不能以任何方式使用它們情组。
  • 在函數(shù)的參數(shù)列表后面加上 =delete 來指出我們希望將它定義為刪除的。=delete 通知編譯器(以及我們代碼的讀者)箩祥,我們不希望定義這些成員
struct NoCopy{
    NoCopy () = default;    // 使用合成的默認構造函數(shù)
    NoCopy (const NoCopy&) = delete;    // 阻止拷貝
    NoCopy &operator=(const NoCopy&) = delete;   // 阻止賦值
    ~NoCopy() = default;   // 使用合成的析構函數(shù) 
};
析構函數(shù)不能是刪除的函數(shù)
  • 對于析構函數(shù)已刪除的類型院崇,不能定義該類型的變量或釋放指向該類型動態(tài)分配對象的指針
struct NoDtor{
    NoDtor () = default;   // 使用默認構造函數(shù)
    ~NoDtor() = delete;   // 我們不能銷毀 NoDtor 類型的對象
}
NoDtor nd;  // 錯誤:NoDtor 的析構函數(shù)是刪除的
NoDtor *p = new NoDtor();    // 正確: 但我們不能 delete p
delete p;    // 錯誤: NoDtor 的析構函數(shù)是刪除的
合成的拷貝控制成員可能是刪除的
  • 本質上,當不可能拷貝袍祖、賦值或銷毀類的成員時底瓣,類的合成拷貝控制成員就被定義為刪除的
private 拷貝控制
  • 在新標準發(fā)布之前,類是通過將其拷貝構造函數(shù)和拷貝賦值運算符聲明為 private 來阻止拷貝
class PrivateCopy{
    // 無訪問說明符蕉陋;接下來的成員默認是 private 
    // 拷貝控制成員是 private 的捐凭,因此普通用戶代碼無法訪問
    PrivateCopy(const PrivateCopy&);
    PrivateCopy &operator=(const PrivateCopy&);
    // 其他成員
public:
    PrivateCopy() = default;   // 使用合成的默認構造函數(shù)
    ~PrivateCopy();    // 用戶可以定義此類型的對象,但無法使用它們
};
  • 由于析構函數(shù)是 public 的凳鬓,用戶可以定義 PrivateCopy 類型的對象茁肠。但是,由于拷貝構造函數(shù)和拷貝賦值運算符是 private 的缩举,用戶代碼將不能拷貝這個類型的對象
建議:希望阻止拷貝的類應該使用 =delete 來定義它們自己的拷貝構造函數(shù)和拷貝賦值運算符垦梆,而不應該將它們聲明為 private 的

13.2 拷貝控制和資源管理

  • 類的行為像一個值,意味著它應該也有自己的狀態(tài)仅孩。當我們拷貝一個像值的對象時托猩,副本和原對象是完全獨立的。改變副本不會對原對象有任何影響辽慕,反之亦然
  • 行為像指針的類則共享狀態(tài)京腥。當我們拷貝一個這種類的對象時,副本和原對象使用相同的底層數(shù)據(jù)溅蛉。改變副本也會改變原對象公浪,反之亦然

13.2.1 行為像值的類 重點

  • C++ Primer 453頁他宛,這一節(jié)太完美了,直接去看書吧因悲,都是重點
  • 講的是深拷貝

13.2.2 定義行為像指針的類 重點

  • C++ Primer 455頁堕汞,這一節(jié)太完美了,直接去看書吧晃琳,都是重點
  • 可以理解為 shared_ptr 的底層實現(xiàn)讯检。(騰訊音樂面試就問到了,可惜當時太菜)

13.3 交換操作

  • C++ Primer 457頁卫旱,這一節(jié)太完美了人灼,直接去看書吧,都是重點
  • 簡而言之顾翼,直接交換兩個對象的話投放,涉及到深拷貝 (重新分配一塊空間,將將新內容放到這塊空間适贸,原空間內容釋放) 這一系列的操作灸芳,很是沒必要。這節(jié)講的是直接交換指向兩塊空間的指針拜姿。
#include<iostream>
using namespace std;

class AA{
    // 友元, 以便訪問 AA 的 private 數(shù)據(jù)成員
    friend void swap(AA &l, AA &r);

    public:
        // 構造函數(shù)
        AA(int bb):aa(bb) {
            std::cout << aa << " 構造函數(shù)" << endl;
        }

        // 拷貝構造函數(shù)
        AA(const AA &temp){
            std::cout << temp.aa << " 拷貝構造函數(shù)" << endl;
            this->aa = temp.aa;
        }

        // 拷貝賦值運算符
        AA& operator=(const AA &temp){
            std::cout << " 拷貝賦值運算符" << endl;
            this->aa = temp.aa;
            return *this;
        }

        ~AA(){
            std::cout << aa << " 析構函數(shù)" << endl;
        }

    private:
        int aa;
};

// 內聯(lián)函數(shù)
inline void swap(AA &l, AA &r){
    std::cout << "交換之前, l.aa = " << l.aa << ", r.aa = " << r.aa << endl;
    std::swap(l.aa, r.aa);
    std::cout << "交換完成, l.aa = " << l.aa << ", r.aa = " << r.aa << endl;
}

int main(){
    AA ff(11);   // 調用構造函數(shù)
    AA gg = ff;  // 調用拷貝構造函數(shù)
    AA hh(ff);   // 調用拷貝構造函數(shù)

    AA kk(77);   // 調用構造函數(shù)
    kk = ff;     // 調用拷貝賦值運算符

    AA a(11111);
    AA b(22222);
    swap(a, b);
}
11 構造函數(shù)
11 拷貝構造函數(shù)
11 拷貝構造函數(shù)
77 構造函數(shù)
 拷貝賦值運算符
11111 構造函數(shù)
22222 構造函數(shù)
交換之前, l.aa = 11111, r.aa = 22222
交換完成, l.aa = 22222, r.aa = 11111
11111 析構函數(shù)
22222 析構函數(shù)
11 析構函數(shù)
11 析構函數(shù)
11 析構函數(shù)
11 析構函數(shù)

13.4 拷貝控制示例

  • 講了一個拷貝控制的小例子烙样,挺好的,建議看看

13.5 動態(tài)內存管理類

  • 實現(xiàn)標準庫 vector 的一個簡化版本蕊肥。功能是不使用模板來實現(xiàn)
  • 類在運行時分配可變大小的內存空間

13.6 對象移動

  • 新標準的一個最主要特性是可以移動而非拷貝對象的能力谒获。很多情況下都會發(fā)生對象的拷貝。在其中某些情況下壁却,對象拷貝后就立即被銷毀了批狱。在這種情況下,移動而非拷貝對象會大幅度提升性能
  • 在上一節(jié)中看到展东,我們的 StrVec 類是這種不必要的拷貝的一個很好的例子赔硫。在重新分配內存的過程中,從舊內存將元素拷貝到新內存是不必要的琅锻,更好的方式是移動元素

13.6.1 右值引用

  • 去看書吧

13.6.2 移動構造函數(shù)和移動賦值運算符

  • 去看書吧

13.6.3 對象移動

  • 去看書吧
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末卦停,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子恼蓬,更是在濱河造成了極大的恐慌,老刑警劉巖僵芹,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件处硬,死亡現(xiàn)場離奇詭異,居然都是意外死亡拇派,警方通過查閱死者的電腦和手機荷辕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門凿跳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疮方,你說我怎么就攤上這事控嗜。” “怎么了骡显?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵疆栏,是天一觀的道長。 經(jīng)常有香客問我惫谤,道長壁顶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任溜歪,我火速辦了婚禮若专,結果婚禮上,老公的妹妹穿的比我還像新娘蝴猪。我一直安慰自己调衰,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布自阱。 她就那樣靜靜地躺著嚎莉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪动壤。 梳的紋絲不亂的頭發(fā)上萝喘,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音琼懊,去河邊找鬼捂掰。 笑死仔雷,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播驱闷,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雇毫!你這毒婦竟也來了恒界?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤车胡,失蹤者是張志新(化名)和其女友劉穎檬输,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匈棘,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡丧慈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逃默。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹃愤,死狀恐怖,靈堂內的尸體忽然破棺而出完域,到底是詐尸還是另有隱情软吐,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布吟税,位于F島的核電站凹耙,受9級特大地震影響,放射性物質發(fā)生泄漏乌妙。R本人自食惡果不足惜使兔,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藤韵。 院中可真熱鬧虐沥,春花似錦、人聲如沸泽艘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匹涮。三九已至天试,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間然低,已是汗流浹背喜每。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雳攘,地道東北人带兜。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像吨灭,于是被迫代替她去往敵國和親刚照。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容

  • 當定義一個類時喧兄,通過顯示或者隱式的指定在此類型的對象拷貝无畔、移動、賦值和銷毀時做什么吠冤。通過定義五種操作來控制這些操作...
    菜雞也會飛閱讀 258評論 0 1
  • 拷貝控制成員函數(shù):拷貝構造浑彰、拷貝賦值、移動構造拯辙、移動賦值闸昨、析構;不顯式定義則編譯器會生成合成版本薄风《希拷貝和移動構造函...
    咸魚翻身ing閱讀 158評論 0 0
  • 3. 類設計者工具 3.1 拷貝控制 五種函數(shù)拷貝構造函數(shù)拷貝賦值運算符移動構造函數(shù)移動賦值運算符析構函數(shù)拷貝和移...
    王偵閱讀 1,804評論 0 1
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學習記錄文檔,今天18年5月份再次想寫文章遭赂,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,754評論 2 9
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,516評論 1 51