主頁 > 後端開發 > 《C++ Primer》筆記 第13章 拷貝控制

《C++ Primer》筆記 第13章 拷貝控制

2021-02-12 05:56:41 後端開發

  1. 拷貝和移動建構式定義了當用同型別的另一個物件初始化本物件時做什么,拷貝和移動賦值運算子定義了將一個物件賦予同型別的另一個物件時做什么,解構式定義了當此型別物件銷毀時做什么,我們稱這些操作為拷貝控制操作

  2. 如果一個建構式的第一個引數是自身型別別的參考,且任何額外引數都有默認值,則此建構式是拷貝建構式

  3. 拷貝建構式的第一個引數必須是一個參考型別,雖然我們可以定義一個接受非const參考的拷貝建構式,但此引數幾乎總是一個const的參考,

  4. 拷貝建構式在幾種情況下都會被隱式地使用,因此,拷貝建構式通常不應該是explicit的,

  5. 如果我們沒有為一個類定義拷貝建構式,編譯器會為我們定義一個,與合成默認建構式不同,即使我們定義了其他建構式,編譯器也會為我們合成一個拷貝建構式,

  6. 一般情況,合成的拷貝建構式會將其引數的成員逐個拷貝到正在創建的物件中,編譯器從給定物件中依次將每個非static成員拷貝到正在創建的物件中,而對于某些類來說,合成拷貝建構式用來阻止我們拷貝該型別別的物件,

  7. 每個成員的型別決定了它如何拷貝:對型別別的成員,會使用其拷貝建構式來拷貝;內置型別的成員則直接拷貝,雖然我們不能直接拷貝一個陣列,但合成拷貝建構式會逐元素地拷貝一個陣列型別的成員,如果陣列元素是型別別,則使用元素的拷貝建構式來進行拷貝,

  8. 當使用直接初始化時,我們實際上是要求編譯器使用普通的函式匹配來選擇與我們提供的引數最匹配的建構式,當我們使用拷貝初始化時,我們要求編譯器將右側運算物件拷貝到正在創建的物件中,如果需要的話還要進行型別轉換,

  9. 拷貝初始化通常使用拷貝建構式來完成,但是,如果一個類有一個移動建構式,則拷貝初始化有時會使用移動建構式而非拷貝建構式來完成,

  10. 拷貝建構式在以下幾種情況下會被使用(發生拷貝初始化):

    • 拷貝初始化(用=定義變數)

    • 將一個物件作為實參傳遞給一個非參考型別的形參

    • 從一個回傳型別為非參考型別的函式回傳一個物件

    • 用花括號串列初始化一個陣列中的元素或一個聚合類中的成員

    • 某些型別別還會對它們所分配的物件使用拷貝初始化,(初始化標準庫容器或呼叫其insert/push操作時,容器會對其元素進行拷貝初始化)

  11. 拷貝建構式被用來初始化非參考型別別引數,這一特性解釋了為什么拷貝建構式自己的引數必須是參考型別,

  12. 如果我們希望使用一個explicit建構式,就必須顯式地使用,

    vector<int> v1(10); // 正確:直接初始化
    vector<int> v2 = 10; // 錯誤:接受大小引數的建構式是explicit的
    void f(vector<int>); // f的引數進行拷貝初始化
    f(10); // 錯誤:不能用一個explicit的建構式拷貝一個實參
    f(vector<int>(10)); // 正確:從一個int直接構造一個臨時vector
    
  13. 在拷貝初始化程序中,編譯器可以(但不是必須)跳過拷貝/移動建構式,直接創建物件,即,編譯器被允許將下面的代碼改寫:

    string null_book = "9-999-9999-9"; // 拷貝初始化
    string null_book("9-999-99999-9"); // 編譯器略過了拷貝建構式
    

    但是,即使編譯器略過了拷貝/移動建構式,但在這個程式點上,拷貝/移動建構式必須是存在且可訪問的(例如,不能是private的),

  14. 多載運算子的引數表示運算子的運算物件,某些運算子,包括賦值運算子,必須定義為成員函式,如果一個運算子是一個成員函式,其左側運算物件就系結到隱式的this引數,對于一個二元運算子,例如賦值運算子,其右側運算物件作為顯式引數傳遞,

  15. 拷貝賦值運算子接受一個與其所在類相同型別的引數:

    class Foo
    {
    public:
    	Foo& operator=(const Foo&); // 賦值運算子
    	// ...
    };
    
  16. 賦值運算子通常應該回傳一個指向其左側運算物件的參考,

  17. 與處理拷貝建構式一樣,如果一個類未定義自己的拷貝賦值運算子,編譯器會為它生成一個合成拷貝賦值運算子

  18. 類似拷貝建構式,對于某些類,合成拷貝賦值運算子用來禁止該型別物件的賦值,如果拷貝賦值運算子并非出于此目的,它會將右側運算物件的每個非static成員賦予左側運算物件的對應成員,這一作業是通過成員型別的拷貝賦值運算子來完成的,對于陣列型別的成員,逐個賦值陣列元素,合成拷貝賦值運算子回傳一個指向其左側運算物件的參考,

    // 等價于合成拷貝賦值運算子
    Sales_data& Sales_data::operator=(const Sales_data &rhs)
    {
    	bookNo = rhs.bookNo; // 呼叫string::operator=
    	units_sold = rhs.units_sold; // 使用內置的int賦值
    	revenue = rhs.revenue; // 使用內置的double賦值
    	return *this; // 回傳一個此物件的參考
    }
    
  19. 解構式釋放物件使用的資源,并銷毀物件的非static資料成員,解構式是類的一個成員函式,名字由波浪號接類名構成,它沒有回傳值,也不接受引數:

    class Foo
    {
    public:
    	~Foo(); // 解構式
    	// ...
    };
    
  20. 由于解構式不接受引數,因此它不能被多載,對一個給定類,只會有唯一一個解構式,

  21. 解構式有一個函式體和一個析構部分,在一個解構式中,首先執行函式體,然后銷毀成員,成員按初始化順序的逆序銷毀,

  22. 在一個解構式中,析構部分是隱式的,成員銷毀時發生什么完全依賴于成員的型別,銷毀型別別的成員需要執行成員自己的解構式,內置型別沒有解構式,因此銷毀內置型別成員什么也不需要做,

  23. 隱式銷毀一個內置指標型別的成員不會delete它所指向的物件,

  24. 無論何時一個物件被銷毀,就會自動呼叫其解構式:

    • 變數在離開其作用域時被銷毀
    • 當一個物件被銷毀時,其成員被銷毀
    • 容器(無論是標準庫容器還是陣列)被銷毀時,其元素被銷毀
    • 對于動態分配的物件,當對指向它的指標應用delete運算子時被銷毀
    • 對于臨時物件,當創建它的完整運算式結束時被銷毀
  25. 當指向一個物件的參考或指標離開作用域時,解構式不會執行,

  26. 類似拷貝建構式和拷貝賦值運算子,對于某些類,合成解構式被用來阻止該型別的物件被銷毀,如果不是這種情況,合成解構式的函式體就為空,

    // 下面的代碼片段等價于Sales_data的合成解構式
    class Sales_data
    {
    public:
    	// 成員會被自動銷毀,除此之外不需要做其他事情
    	~Sales_data() { }
    	// 其他成員的定義,如前
    };
    
  27. 解構式體自身并不直接銷毀成員,成員是在解構式體之后隱含的析構階段中被銷毀的,在整個物件銷毀程序中,解構式體是作為成員銷毀步驟之外的另一部分而進行的,

  28. 當我們決定一個類是否要定義它自己版本的拷貝控制成員時,一個基本原則是首先確定這個類是否需要一個解構式,通常,對解構式的需求要比對拷貝建構式或賦值運算子的需求更為明顯,如果一個類需要自定義解構式,幾乎可以肯定它也需要自定義拷貝賦值運算子和拷貝建構式

  29. 決定一個類是否要定義它自己版本的拷貝控制成員時,第二個基本原則:如果一個類需要一個拷貝建構式,幾乎可以肯定它也需要一個拷貝賦值運算子,反之亦然——如果一個類需要一個拷貝賦值運算子,幾乎可以肯定它也需要一個拷貝建構式,然而,無論是需要拷貝建構式還是需要拷貝賦值運算子都不必然意味著也需要解構式,

  30. 我們可以通過將拷貝控制成員定義為=default來顯式地要求編譯器生成合成的版本,

  31. 當我們在類內用=default修飾成員的宣告時,合成的函式將隱式地宣告為行內的(就像任何其他類內宣告的成員函式一樣),如果我們不希望合成的成員是行內函式,應該只對成員的類外定義使用=default

    class Sales_data
    {
    public:
    	// 拷貝控制成員;使用default
    	Sales_data() = default;
    	Sales_data(const Sales_data&) = default;
    	Sales_data& operator=(const Sales_data &);
    	~Sales_data() = default;
    	// 其他成員的定義,如前
    };
    Sales_data& Sales_data::operator=(const Sales_data&) = default;
    
  32. 我們只能對具有合成版本的成員函式使用=default(即,默認建構式或拷貝控制成員),

  33. 我們可以通過將拷貝建構式和拷貝賦值運算子定義為洗掉的函式來阻止拷貝:我們雖然宣告了它們,但不能以任何方式使用它們,在函式的引數串列后面加上=delete來指出我們希望將它定義為洗掉的:

    struct NoCopy
    {
    	NoCopy() = default; // 使用合成的默認建構式
    	NoCopy(const NoCopy&) = delete; // 阻止拷貝
    	NoCopy &operator=(const NoCopy&) = delete; // 阻止賦值
    	~NoCopy() = default; // 使用合成的解構式
    	// 其他成員
    };
    
  34. =default不同,=delete必須出現在函式第一次宣告的時候,這個差異與這些宣告的含義在邏輯上是吻合的,一個默認的成員只影響為這個成員而生成的代碼,因此=default直到編譯器生成代碼時才需要,而另一方面,編譯器需要知道一個函式是洗掉的,以便禁止試圖使用它的操作,

  35. =default的另一個不同之處是,我們可以對任何函式指定=delete(我們只能對編譯器可以合成的默認建構式或拷貝控制成員使用=default),雖然洗掉函式的主要用途是禁止拷貝控制成員,但當我們希望引導函式匹配程序時,洗掉函式有時也是有用的,

  36. 對于一個洗掉了解構式的型別,編譯器將不允許定義該型別的變數或創建該類的臨時物件,而且,如果一個類有某個成員的型別洗掉了解構式,我們也不能定義該類的變數或臨時物件,

  37. 對于洗掉了解構式的型別,雖然我們不能定義這種型別的變數或成員,但可以動態分配這種型別的物件,但是,不能釋放這些物件:

    struct NoDtor
    {
    	NoDtor() = default; // 使用合成默認建構式
    	~NoDtor() = delete; // 我們不能銷毀NoDtor型別的物件
    };
    NoDtor nd; // 錯誤:NoDtor的解構式是洗掉的
    NoDtor *p = new NoDtor(); // 正確:但我們不能delete p
    delete p; // 錯誤:NoDtor的解構式是洗掉的
    
  38. 對于解構式已洗掉的型別,不能定義該型別的變數或釋放指向該型別動態分配物件的指標,

  39. 對某些類來說,編譯器將這些合成的成員定義為洗掉的函式:

    • 如果類的某個成員的解構式是洗掉的或不可訪問的(例如,是private的),則類的合成解構式被定義為洗掉的,
    • 如果類的某個成員的拷貝建構式是洗掉的或不可訪問的,則類的合成拷貝建構式被定義為洗掉的,如果類的某個成員的解構式是洗掉的或不可訪問的,則類合成的拷貝建構式也被定義為洗掉的,
    • 如果類的某個成員的拷貝賦值運算子是洗掉的或不可訪問的,或是類有一個const的或參考成員,則類的合成拷貝賦值運算子被定義為洗掉的,
    • 如果類的某個成員的解構式是洗掉的或不可訪問的,或是類有一個參考成員,它沒有類內初始化器,或是類有一個const成員,它沒有類內初始化器且其型別未顯式定義默認建構式,則該類的默認建構式被定義為洗掉的,
  40. 本質上,這些規則的含義是:如果一個類有資料成員不能默認構造、拷貝、賦值或銷毀,則對應的成員函式將被定義為洗掉的,(本質上,當不可能拷貝、賦值或銷毀類的成員時,類的合成拷貝控制成員就被定義為洗掉的,)

  41. 因為試圖訪問一個未定義的成員會導致一個鏈接時錯誤,通過宣告(但不定義)private的拷貝建構式,我們可以預先阻止任何拷貝該型別物件的企圖:試圖拷貝物件的用戶代碼將在編譯階段被標記為錯誤;成員函式或友元函式中的拷貝操作將會導致鏈接時錯誤,

  42. 希望阻止拷貝的類應該使用=delete來定義它們自己的拷貝建構式和拷貝賦值運算子,而不應該將它們宣告為private的,

  43. 通常,管理類外資源的類必須定義拷貝控制成員,為了定義這些成員,我們首先必須確定此型別物件的拷貝語意,一般來說,有兩種選擇:可以定義拷貝操作,使類的行為看起來像一個值或者像一個指標,

    • 類的行為像一個值,意味著它應該也有自己的狀態,當我們拷貝一個像值的物件時,副本和原物件是完全獨立的,改變副本不會對原物件有任何影響,反之亦然,
    • 行為像指標的類則共享狀態,當我們拷貝一個這種類的物件時,副本和原物件使用相同的底層資料,改變副本也會改變原物件,反之亦然,
  44. 關鍵概念:賦值運算子

    當你撰寫賦值運算子時,有兩點需要記住:

    • 如果將一個物件賦予它自身,賦值運算子必須能正確作業,
    • 大多數賦值運算子組合了解構式和拷貝建構式的作業,

    當你撰寫一個賦值運算子時,一個好的模式是先將右側運算物件拷貝到一個區域臨時物件中,當拷貝完成后,銷毀左側運算物件的現有成員就是安全的了,一旦左側運算物件的資源被銷毀,就只剩下將資料從臨時物件拷貝到左側運算物件的成員中了,

    HasPtr& HasPtr::operator=(const HasPtr &rhs)
    {
    	auto newp = new string(*rhs.ps); // 拷貝底層string
    	delete ps; // 釋放舊記憶體
    	ps = newp; // 從右側運算物件拷貝資料到本物件
    	i = rhs.i;
    	return *this; // 回傳本物件
    }
    

    這樣撰寫賦值運算子是錯誤的

    HasPtr& HasPtr::operator=(const HasPtr &rhs)
    {
    	delete ps; // 釋放物件指向的string
    	// 如果rhs和*this是同一個物件,我們就將從已釋放的記憶體中拷貝資料!
    	ps = new string(*(rhs.ps));
    	i = rhs.i;
    	return *this;
    }
    
  45. 令一個類展現類似指標的行為的最好方法是使用shared_ptr來管理類中的資源,但是,有時我們希望直接管理資源,在這種情況下,使用參考計數就很有用了,參考計數的作業方式如下:

    • 除了初始化物件外,每個建構式(拷貝建構式除外)還要創建一個參考計數,用來記錄有多少物件與正在創建的物件共享狀態,當我們創建一個物件時,只有一個物件共享狀態,因此將計數器初始化為1,
    • 拷貝建構式不分配新的計數器,而是拷貝給定物件的資料成員,包括計數器,拷貝建構式遞增共享的計數器,指出給定物件的狀態又被一個新用戶所共享,
    • 解構式遞減計數器,指出共享狀態的用戶少了一個,如果計數器變為0,則解構式釋放狀態,
    • 拷貝賦值運算子遞增右側運算物件的計數器,遞減左側運算物件的計數器,如果左側運算物件的計數器變為0,意味著它的共享狀態沒有用戶了,拷貝賦值運算子就必須銷毀狀態,
  46. 實作共享計數器的一種方法是將計數器保存在動態記憶體中,

    class HasPtr
    {
    public:
        // 建構式分配新的string和新的計數器,將計數器置為1
        HasPtr(const string &s = string()) : ps(new string(s)), i(0), use(new size_t(1)) {}
        // 拷貝建構式拷貝所有三個資料成員,并遞增計數器
        HasPtr(const HasPtr &p) : ps(p.ps), i(p.i), use(p.use) { ++*use; }
        HasPtr &operator=(const HasPtr &);
        ~HasPtr();
    
    private:
        string *ps;
        int i;
        size_t *use; // 用來記錄有多少個物件共享*ps的成員
    };
    HasPtr &HasPtr::operator=(const HasPtr &rhs)
    {
        ++*rhs.use;      // 遞增右側運算物件的參考計數
        if (--*use == 0) // 然后遞減本物件的參考計數
        {
            delete ps;  // 如果沒有其他用戶
            delete use; // 釋放本物件分配的成員
        }
        ps = rhs.ps; // 將資料從rhs拷貝到本物件
        i = rhs.i;
        use = rhs.use;
        return *this; // 回傳本物件
    }
    HasPtr::~HasPtr()
    {
        if (--*use == 0) // 如果參考計數變為0
        {
            delete ps;  // 釋放string記憶體
            delete use; // 釋放計數器記憶體
        }
    }
    
  47. 除了定義拷貝控制成員,管理資源的類通常還定義一個名為swap的函式,對于那些與重排元素順序的演算法一起使用的類,定義swap是非常重要的,這類演算法在需要交換兩個元素時會呼叫swap,

  48. 如果一個類定義了自己的swap,那么演算法將使用類自定義版本,否則,演算法將使用標準庫定義的swap,

    class HasPtr
    {
    	friend void swap(HasPtr&, HasPtr&);
    	// 其他成員定義
    };
    
    // 由于swap的存在就是為了優化代碼,我們將其宣告為inline函式,
    inline void swap(HasPtr &lhs, HasPtr &rhs) 
    {
    	using std::swap;
    	swap(lhs.ps, rhs.ps); // 交換指標,而不是string資料
    	swap(lhs.i, rhs.i); // 交換int成員
    }
    
  49. 與拷貝控制成員不同,swap并不是必要的,但是,對于分配了資源的類,定義swap可能是一種很重要的優化手段,

  50. swap函式應該呼叫swap,而不是std::swap,內置型別是沒有特定版本的swap的,對swap的呼叫會呼叫標準庫std::swap,但是,如果一個類的成員有自己型別特定的swap函式,呼叫std::swap就是錯誤的了,

  51. 如果存在型別特定的swap版本,swap呼叫會與之匹配,如果不存在型別特定的版本,則會使用std中的版本(假定作用域中有using宣告),

  52. 定義swap的類通常用swap來定義它們的賦值運算子,這些運算子使用了一種名為拷貝并交換的技術,這種技術將左側運算物件與右側運算物件的一個副本進行交換:

    // 注意rhs是按值傳遞的,意味著HasPtr的拷貝建構式...
    // ...將右側運算物件中的string拷貝到rhs
    HasPtr& HasPtr::operator=(HasPtr rhs)
    {
    	// 交換左側運算物件和區域變數rhs的內容
    	swap(*this, rhs); // rhs現在指向本物件曾經使用的記憶體
    	return *this; // rhs被銷毀,從而delete了rhs中的指標
    }
    
  53. 使用拷貝并交換的賦值運算子自動就是例外安全的,且能正確處理自賦值,

  54. 雖然通常來說分配資源的類更需要拷貝控制,但資源管理并不是一個類需要定義自己的拷貝控制成員的唯一原因,一些類也需要拷貝控制成員的幫助來進行簿記作業或其他操作,

  55. 拷貝賦值運算子通常執行拷貝建構式和解構式中也要做的作業,這種情況下,公共的作業應該放在private的工具函式中完成,

  56. 拷貝控制示例

    • Folder.cpp

    #include <utility>   
    // for move, we don't supply a using declaration for move
    
    #include <iostream>
    using std::cerr; using std::endl;
    
    #include <set>
    using std::set; 
    
    #include <string>
    using std::string; 
    
    #include "Folder.h"
    
    void swap(Message &lhs, Message &rhs)
    {
    	using std::swap;  // not strictly needed in this case, but good habit
    
    	// remove pointers to each Message from their (original) respective Folders
    	for (auto f: lhs.folders) 
    		f->remMsg(&lhs);
    	for (auto f: rhs.folders) 
    		f->remMsg(&rhs);
    
    	// swap the contents and Folder pointer sets
    	swap(lhs.folders, rhs.folders);   // uses swap(set&, set&)
    	swap(lhs.contents, rhs.contents); // swap(string&, string&)
    
    	// add pointers to each Message to their (new) respective Folders
    	for (auto f: lhs.folders) 
    		f->addMsg(&lhs);
    	for (auto f: rhs.folders) 
    		f->addMsg(&rhs);
    }
    
    Folder::Folder(Folder &&f)
    {
    	move_Messages(&f);   // make each Message point to this Folder
    }
    
    Folder& Folder::operator=(Folder &&f) 
    {
    	if (this != &f) {
    		remove_from_Msgs();  // remove this Folder from the current msgs
    		move_Messages(&f);   // make each Message point to this Folder
    	}
    	return *this;
    }
    
    void Folder::move_Messages(Folder *f)
    {
    	msgs = std::move(f->msgs); // move the set from f to this Folder
    	f->msgs.clear(); // ensure that destroying f is harmless
    	for (auto m : msgs) {  // for each Message in this Folder
    		m->remFldr(f);     // remove the pointer to the old Folder
    		m->addFldr(this);  // insert pointer to this Folder
    	}
    }
    
    Message::Message(Message &&m): contents(std::move(m.contents))
    {
    	move_Folders(&m); // moves folders and updates the Folder pointers
    }
    
    Message::Message(const Message &m): 
        contents(m.contents), folders(m.folders) 
    {
        add_to_Folders(m); // add this Message to the Folders that point to m
    }
    
    Message& Message::operator=(Message &&rhs) 
    {
    	if (this != &rhs) {       // direct check for self-assignment
    		remove_from_Folders();
    		contents = std::move(rhs.contents); // move assignment
    		move_Folders(&rhs); // reset the Folders to point to this Message
    	}
        return *this;
    }
    
    Message& Message::operator=(const Message &rhs)
    {
    	// handle self-assignment by removing pointers before inserting them
        remove_from_Folders();    // update existing Folders
        contents = rhs.contents;  // copy message contents from rhs
        folders = rhs.folders;    // copy Folder pointers from rhs
        add_to_Folders(rhs);      // add this Message to those Folders
        return *this;
    }
    
    Message::~Message()
    {
        remove_from_Folders();
    }
    
    // move the Folder pointers from m to this Message
    void Message::move_Folders(Message *m)
    {
    	folders = std::move(m->folders); // uses set move assignment
    	for (auto f : folders) {  // for each Folder 
    		f->remMsg(m);    // remove the old Message from the Folder
    		f->addMsg(this); // add this Message to that Folder
    	}
    	m->folders.clear();  // ensure that destroying m is harmless
    }
    
    // add this Message to Folders that point to m
    void Message::add_to_Folders(const Message &m)
    {
    	for (auto f : m.folders) // for each Folder that holds m
            f->addMsg(this); // add a pointer to this Message to that Folder
    }
    
    // remove this Message from the corresponding Folders 
    void Message::remove_from_Folders()
    {
    	for (auto f : folders)  // for each pointer in folders
    		f->remMsg(this);    // remove this Message from that Folder
    	folders.clear();        // no Folder points to this Message
    
    }
    
    void Folder::add_to_Messages(const Folder &f)
    {
    	for (auto msg : f.msgs)
    		msg->addFldr(this);   // add this Folder to each Message
    }
    
    Folder::Folder(const Folder &f) : msgs(f.msgs)
    {
        add_to_Messages(f);  // add this Folder to each Message in f.msgs
    }
    
    Folder& Folder::operator=(const Folder &f)
    {
        remove_from_Msgs();  // remove this folder from each Message in msgs
    	msgs = f.msgs;       // copy the set of Messages from f
        add_to_Messages(f);  // add this folder to each Message in msgs
        return *this;
    }
    
    Folder::~Folder()
    {
        remove_from_Msgs();
    }
    
    
    void Folder::remove_from_Msgs()
    {
        while (!msgs.empty()) 
            (*msgs.begin())->remove(*this);
    }
    void Message::save(Folder &f)
    {
        folders.insert(&f); // add the given Folder to our list of Folders
        f.addMsg(this);     // add this Message to f's set of Messages
    }
    
    void Message::remove(Folder &f)
    {
        folders.erase(&f); // take the given Folder out of our list of Folders
        f.remMsg(this);    // remove this Message to f's set of Messages
    }
    
    void Folder::save(Message &m)
    {
        // add m and add this folder to m's set of Folders
        msgs.insert(&m);
        m.addFldr(this);
    }
    
    void Folder::remove(Message &m)
    {
        // erase m from msgs and remove this folder from m
        msgs.erase(&m);
        m.remFldr(this);
    }
    
    void Folder::debug_print()
    {
        cerr << "Folder contains " << msgs.size() << " messages" << endl;
        int ctr = 1;
        for (auto m : msgs) {
            cerr << "Message " << ctr++ << ":\n\t" << m->contents << endl;
    	}
    }
    
    void Message::debug_print()
    {
        cerr << "Message:\n\t" << contents << endl;
        cerr << "Appears in " << folders.size() << " Folders" << endl;
    }
    
    • Folder.h

    #ifndef FOLDER_H
    #define FOLDER_H
    
    #include <string>
    #include <set>
    
    class Folder;
    
    class Message
    {
    	friend void swap(Message&, Message&);
    	friend class Folder;
    public:
        // folders is implicitly initialized to the empty set 
        explicit Message(const std::string &str = ""): 
    		contents(str) { }  
    
        // copy control to manage pointers to this Message
        Message(const Message&);             // copy constructor
        Message& operator=(const Message&);  // copy assignment
        ~Message();                          // destructor
        Message(Message&&);            // move constructor
        Message& operator=(Message&&); // move assignment
    
        // add/remove this Message from the specified Folder's set of messages
        void save(Folder&);   
        void remove(Folder&); 
        void debug_print(); // print contents and it's list of Folders, 
                            // printing each Folder as well
    private:
        std::string contents;      // actual message text
        std::set<Folder*> folders; // Folders that have this Message
    
        // utility functions used by copy constructor, assignment, and destructor
        // add this Message to the Folders that point to the parameter
        void add_to_Folders(const Message&);
    	void move_Folders(Message*);
        // remove this Message from every Folder in folders
        void remove_from_Folders(); 
    
        // used by Folder class to add self to this Message's set of Folder's
        void addFldr(Folder *f) { folders.insert(f); }
        void remFldr(Folder *f) { folders.erase(f); }
    };
    // declaration for swap should be in the same header as Message itself
    void swap(Message&, Message&);
    
    class Folder
    {
    	friend void swap(Message&, Message&);
    	friend class Message;
    public:
        ~Folder(); // remove self from Messages in msgs
        Folder(const Folder&); // add new folder to each Message in msgs
        Folder& operator=(const Folder&); // delete Folder from lhs messages
                                          // add Folder to rhs messages
        Folder(Folder&&);   // move Messages to this Folder 
        Folder& operator=(Folder&&); // delete Folder from lhs messages
                                     // add Folder to rhs messages
    
        Folder() = default; // defaults ok
    
        void save(Message&);   // add this message to folder
        void remove(Message&); // remove this message from this folder
        
        void debug_print(); // print contents and it's list of Folders, 
    private:
        std::set<Message*> msgs;  // messages in this folder
    
        void add_to_Messages(const Folder&);// add this Folder to each Message
        void remove_from_Msgs();     // remove this Folder from each Message
        void addMsg(Message *m) { msgs.insert(m); }
        void remMsg(Message *m) { msgs.erase(m); }
    	void move_Messages(Folder*); // move Message pointers to point to this Folder
    };
    
    #endif
    

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/258877.html

標籤:其他

上一篇:30行代碼實作朋友圈自動點贊

下一篇:Java三大特征詳解

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more