假設有一個 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)