精華區beta C_and_CPP 關於我們 聯絡資訊
假設有一個 class C 如下,例子很簡單,只寫出 copy ctor 的部份 class C { public : ~C() { ... } ... C(const C &rhs) : Len(rhs.Len), Buf(Len>0 ? new char[len] : 0) { if (Buf) { memcpy(Buf, rhs.Buf, len); } } ... private : int Len; char *Buf; }; 由於 class C 內部使用動態配置,也就是說 C++ 預設的 memberwise copy 不能正確 work。現在問題是,它的 copy assignment,也就是 operator= 要怎麼實現呢? 這裏推薦一個來自 Exceptional C++ 的觀念,就是儘可能以 copy ctor 來實現 copy assignment,最主要的理由是一致性,既然兩者都做一樣的 事,就不應該有兩種不同的作法。 常見的作法是在 class C 內部使用一個 private 的 copy 函式,再讓 copy ctor 和 copy assignment 都去呼叫它就好了。事實上,這的確是一個很好 的辦法,也幾乎沒有任何問題。 那為什麼還要強調「以 copy ctor 來實現 copy assignment」這件事呢? 主要在於「建構」物件的動作和「指派」還是略有些許不同,例如前者可以 在 member initialization list 中將成員初始化,但後者就不行,所以如 果倒過來,用 copy assignment 實現 copy ctor,就失去利用成員初始化 串列的可能性了。另外還有一個重要的理由是為了符合 RAII 的精神,這點 在後面會解釋。 Exceptional C++ 這本書上很詳細地解釋了一個很高明,但是卻是不好的 實作方式,就是例用 placement new 的技巧。關於它為什麼不好,就不 多說了,主要是不符合 Exception-Safety,而且還有其他潛在的毛病。 這裏直接介紹理想的作法,是透過一個 class C 內部的 Swap 函式來達 成的: C &Swap(C &rhs) throw() { std::swap(Len, rhs.Len); std::swap(Buf, rhs.Buf); return *this; } 有了以上的 Swap 函式之後,class C 的 copy assignment 就可以很簡 潔的以如下的方式實現: C &operator=(const C &rhs) // 版本1,這是推薦最理想的實作版本 { return Swap(C(rhs)); } 能夠一眼就看穿以上程式碼內部在搞什麼名堂的,相信已 C++ 程度已經 相當不錯了。但即使如此,如果沒有深思熟慮,對版本1的各種優點, 還是很容易忽略掉。 首先,當然,最重要的,它符合了「一致性」的基本原則,就是不重覆 copy ctor 已經完成的工作,並且也不浪費 copy ctor 可以利用建構式 各種特性的優勢。 其次,大家有沒有注意到,以這種手法實現的 copy assignment,它不 需要做 clear 的動作,這和許多常見的實作碼不同,例如: C &operator=(const C &rhs) // 版本2,常見的版本 { Clear(); // 假設 *this 原先已有配置記憶體,就先清掉 Copy(*this, rhs); // 呼叫內部的 Copy 函式來完成複製的動作 return *this; } 常見的版本當然也沒什麼問題。倒是許多 C++ 新手常會忘了 copy assignment 內要有類似 Clear 的檢查動作。(以免 *this 配置的記憶體遺失掉) 講到這又不得不提一下如果倒過來,「以 copy assignment 實現 copy ctor 」的話,Clear 對 copy ctor 就明顯是多餘的動作了(也就是比版本2還差 一點) 版本1高明的地方,就是它巧妙了運用了 RAII 的精神,讓解構式的自動化 機制去做資源管理,不管 *this 物件原本有沒有動態配置,最後一定會被 正確處理。 此外,有了 Swap 函式之後,class C 的 Clear 函式也可以簡潔安全的以 如下的方式實現: void Clear() { Swap(C()); } 最後,也是版本1最厲害的地方,就是它是 Exception Safe 的!當然,這 有個但書,就是 Swap 函式必須保證不丟出異常才行,否則就沒有什麼異常 安全可言。在這個簡單的例子中,Swap 顯然沒有半點機會丟出異常,但若 class C 內部有其他擁有 ctor 的成員,就難講了。但這並不是什麼缺陷, 而是完全合理的,因為天底下沒有白吃的午餐,如果 class C 的 member 的指派動作會出狀況,那要求 class C 的指派動作符合異常安全當然是不可 能的。 另外,版本1還有更簡潔的實作碼如下: C &operator=(C rhs) // 版本3,其實和版本1是完全相同的 { return Swap(rhs); } 這也是書上給的例子,特別提出來只是想提醒一下,如果哪天你看到某段程 式的實作碼,它的 copy assignment 的參數是傳物件而不是傳 reference to const 物件,不要馬上就認定它是錯的。因為也許它是出自高手級的實作 碼喔!但為了避免誤會,還是鼓勵大家多用版本1的寫法。 雖然版本1最偉大(了不起,其他方法難以達成)的地方在於它是符合異常 安全的,但我個人認為最重要的是它應用了 RAII 的精神和符合「一致性」 原則,大家可以多多玩味。 -- ※ 編輯: gocpp 來自: 59.120.214.120 (08/21 05:51)