C++自修入門實境秀、C++ Primer 5版研讀秀 106/ ~第13章 拷貝控制13.4. A Copy-Control Example-...
頁521
The Message Class
第106集開始 17:30
有了這樣的構思,我們就可以開始撰寫這樣的Message類別:
class Message
{
friend claa Folder;
public:
//會直接將floder物件內的set初始化為空的容器
explicit Message(const std::string &str = "") : contexts(str) {}
//拷貝控制管控指向這個Message物件的指標
Message(const Message &); //拷貝建構器
Message &operator=(const Message &); //拷貝指定運算子
~Message(); //解構器
//從這個Message物件所在的資料夾中新增/移除這個Message物件(新增/清除該Folder物件內的set容器內指向這則Message物件的指標元素)
void save(Folder &);
void remove(Folder &);
private:
std::string contents; //這個訊息物件Message的實際內容
std::set<Folder *> folders; //這則訊息(Message物件)會存在的資料夾目錄
//利用拷貝控制成員(拷貝建構器、拷貝指定運算子、解構器))寫成的工具函式(utility functions),來將這則訊息Message物件新增到Folder指標指向的位置
void add_to_Folders(const Message &);
//從folders這個set容器中的所有資料夾,移除這個訊息
void remove_from_Folders();
};
57:15
此Message類別定義了二個資料成員。contents是用來儲存訊息內容的,而folders則是存放了那些這個Message的指標所在的資料夾的指標(其中元素型別為「*Folder」)。帶了一個string作引數的建構器會將這個引數拷貝到contents中,且直接(implicitly)將folders容器初始化為一個空的set。因為這個建構器只有一個引數、且有預設引數,故在用到它時這個引數是可以省略而不必帶入的,所以它也可以是Message的預設建構器(§7.5.1,頁290)。
24:30 1:2:37
The save and remove Members
成員函式save與remove
除了拷貝控制的成員,這個Message類別還有二個公開的成員函式。save的功能是將這個Message物件放到指定的資料夾目錄中,而remove則是將資料夾存放的這個Message物件的指標全部清除:
void Message::save(Folder &f)
{
folders.insert(&f); //用set類別的成員函式insert將一個資料夾目錄加到這個Message所在的目錄清單中 「&」係取址運算子
f.addMsg(this); //將這個Message的指標用Folder類別的addMsg成員函式來加到f資料夾目錄的訊息列表中
}
void Message::remove(Foder &f)
{
folders.erase(&f); //從這個Message的目錄指標清單中移除f這個目錄指標。「&」係取址運算子。
f.remMsg(this); //從f目錄的訊息指標清單中移除這則訊息的指標
}
頁522
1:8:19
要儲存或移除一則訊息(Message物件)就需要更新這則訊息的folders資料成員的內容(值)。要新增這則訊息到某個資料夾目錄中時,我們就將指向這則訊息的指標存到它該存放到的資料夾目錄中。若要移除這則訊息,則從資料夾目錄所儲存的訊息指標清單中,移除這則訊息的指標。
這些操作都須顧到要同步更新相對應的資料夾(即Folder物件)內容。Folder物件的更新動作是交由Folder類別的addMsg和remMsg這兩個成員函式來執行的;它們會在這個Message物件的指標所在的資料夾目錄(Folder物件)上新增加或移除掉這個指標。
38:58 1:14:43
Copy Control for the Message Class
Message類別的拷貝控制成員
對Message物件進行拷貝後,拷貝出來的副本也該在拷貝原本所在的資料夾目錄中出現。因此我們就必須要在原本的Message物件所儲存的set(所在資料夾目錄指標清單),一一找出它所載的Folder指標所指向的Folder物件來將指向這個副本的指標給新增進去。拷貝建構器和拷貝指定運算子都須用到這樣的運算,因此我們可以另外獨立定義出一個函式來專責處理這兩個拷貝控制成員都要用到的運算:
//將這個Message的副本加到其原本訊息所在的資料夾目錄中
void Message::add_to_Folders(const Message &m)
{
for (auto f : m.folders) //對m所在的資料夾目錄作逐一地巡覽;「auto」型別為「*Folder」。
f->addMsg(this); //將這個訊息指標新增到原訊息所在的資料夾目錄中
}
這裡我們在m的folders成員內的每個Folder指標上,用箭號運算子來調用Folder的addMsg成員函式。這個函式會把這則副本訊息的指標新增到這個Folder的訊息指標列表的容器set(這個資料成員)中。
1:20:32
忘記錄的部分就看臉書直播第552集 https://www.facebook.com/oscarsun72/videos/2674747232636394/
Message的拷貝建構器會拷貝其Message物件內的所有資料成員:
Message::Message(const Message &m) : contemts(m.contents), folders(m.folders)
{
add_to_Folders(m); //將這則拷貝出來的Message加到它要存放的資料夾目錄
}
拷貝建構器也會調用add_to_Folders這個工具函式(utility functions)來將這個新拷貝出來的訊息指標,存放到其拷貝來源所存放的資料夾目錄中。
57:03 1:24:32 1:37:58
The Message Destructor
Message類別的解構器
在刪除一則訊息時(即解構一個Message物件時),也必須同時將它所在的資料夾目錄,所存有的所有指向它的指標都給清除。而拷貝指定運算子也需用到這樣的操作,因此我們也可以另外再定義出一個通用的函式(即工具函式,utility functions)來執行這樣共通的操作:
void Message::remove_form_Folders()
{
for (auto f : folders) //對存在folders這個set型別的資料成員中的每個「*Folder」(Folder指標)
f->remMsg(this); //將「*f」(解參考f這個Folder指標)內存放的這個Message的指標(即「this」指標)給清除掉
folders.clear(); //將這個folders set容器的資料成員的內容物也清除掉,就表示不再有資料夾目錄會再有指向這則訊息(這個Message物件)的指標了
}
頁523
這個remove_from_Folders實作起來很像add_to_Folders,只不過它不是用Folder類別的addMsg成員函式,而是用remMsg來移除現在這個Message物件。
1:32:26 1:42:26
既然有了像remove_from_Folders這樣的工具函式,Message的解構器撰寫起來就精簡多了:
Message::~Message()
{
remove_from_Folders();
}
2:18:32
引用了remove_from_Folders就能確保不再有資料夾目錄還會有指向這個被摧毀的Message物件的指標。而對於另外兩個資料成員,編譯器自會調用string類別的解構器來釋放contents,而用set類別的來清除folders所佔用的資源。
1:47:11 2:20:29
Message Copy-Assignment Operator
Message類別的拷貝指定運算子
也和大多數的指定運算子一樣,Message類別的拷貝指定運算子也必須要做到拷貝建構器與解構器所要做的事。【英文版Message誤作Folder】而在定義這樣的指定運算子時,也要確保它能在左、右兩邊運算元是同一物件時,還能正常運作。
為了確保自拷貝與自指定能正常運行,我們會先移除左運算元上的folders成員所有的Folder指標,然後才將右運算元中的folders成員指定給它。
1:57:59 2:27:15
Message &Message::operator=(const Message &rhs)
{
//為了自指定自拷貝的安全考量,我們會在指定給左運算元新的指標前,先移除左運算元上原有的指標。
remove_from_Folders(); //更新現有的Folder物件,清除它們儲存的指向左運算元的指標。
contents = rhs.contents; //從rhs(右運算元)將contents(訊息內容)拷貝過來
folders = rhs.folders; //從rhs將這個訊息對應到的所有Folder的指標給拷貝過來
add_to_Folders(rhs); //將這則訊息加到所有它要在的資料夾中。「rhs」應也可以用「*this」來代入。因為此時rhs和*this的folders(經過上一行後)已然相同了。
return *this;
}
一旦左、右雙方的運算元是同一個物件,那麼它們就會有相同的記憶體位址(應是指this指標,因為remove_from_Folders會用這個this作為引數去清除)。萬一我們在add_to_folders之後才呼叫remove_from_folders,我們就會誤將所有資料夾中儲存的這則訊息的指標給移除了。
Had we called remove_from_folders after calling add_to_folders, we would have removed this Message from all of its corresponding Folders.
【中文版翻譯有誤:假設我們在呼叫add_to_Folders 之後才呼叫remove_from_Folders,我們就已經從它對應的所有Folder移除了這個Message。】
2:40:07
忘記錄的部分就看臉書直播第552集 https://www.facebook.com/oscarsun72/videos/2674747232636394/ 約4時19分處
A swap Function for Message
Message類別專用的swap函式
對Message的資料成員型別的string、set類別,程式庫都已經定義了它們可用的swap(§9.2.5,頁339),因此,若Message也能定義自己的swap,想必會有一定的方便;因為有了這個swap,就能省去對其資料成員(contents和folders)多做些不必要的拷貝運算。
只是這個swap函式也要能處理那些指向經過swap後的Message的Folder指標。也就是說在經過swap後,如swap(m1,m2),原來有著指向m1的Folder指標此時就必須改為指向m2了,反之亦然。
頁524
我們會對folders成員處理兩次,用這個方式來處理這些Folder指標。第一次會從相關的Folder物件中移除其Message指標,接著調用swap來調換其中的資料成員。然後再第二次處理folders,【英文版誤作「folders(folderS)」,中文版卻不誤,且翻得不錯!】,這次就是將指標加到已經置換後的Message物件中。(也就是第一次先清空原有的指標,對調後,再加入該有的指標)
We’ll manage the Folder pointers by making two passes through each of the folders members. The first pass will remove the Messages from their respective Folders. We’ll next call swap to swap the data members. We’ll make the second pass through folders this time adding pointers to the swapped Messages:
我們會對每個folders成員做兩回處理,以管理那些Folder指標。第一回會從它們分別對應的Folder移除Message。接著我們會呼叫swap來對調資料成員。這次我們會對folders進行第二回處理,新增指標到調換過的Message:(中文版)
pass在這裡是翻成「遍」
making two passes through:通過做兩遍
2:58:20 3:7:51
void swap(Message &lhs, Message &rhs)
{
using std::swap; //雖然在此例中不必用到這個,但還是養成好習慣,把它寫出來
//從這兩個要對調的Message物件它們本來存在的資料夾目錄中移除指向它們的指標
for (auto f : lhs.folders)
f->remMsg(&lhs);
for (auto f : rhs.folders)
f->remMsg(&rhs);
//對調資料成員contents和folders(即由Folder指標組成的set容器)
swap(lhs.folders, rhs.folders); // 用set容器所定義的swap來對調,即swap(set&,set&)
swap(lhs.contents, rhs.contents); //swap(string&,string&)利用程式庫對string類別定義的swap來做contents的對調
//在這兩個對調的Message該放到的資料夾目錄中新增指向它們的指標
for (auto f : lhs.folders)
f->addMsg(&lhs);
for (auto f : rhs.folders)
f->addMsg(&rhs);
}
留言