在瞭解到異常安全的重要性的重要性後,馬上想到自己在剛學C++的時候,在單鏈表上所做的嘗試,記得那個慘不忍睹的賦值函數是這樣寫的:
template <class Temp>
LinkList<Temp> & LinkList<Temp>::operator=(LinkList<Temp> &List)
{
DestoryList();
LinkNode<Temp> *p = List.Head;
LinkNode<Temp> *h = Head;
while (p != NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
if(t==NULL)
{
cerr << "error !" << endl;
exit(1);
}
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
return *this;
}
如果new不拋出異常,正常的運行沒什麼大的問題,但是當new拋出異常時,那麼就完蛋了。在拋出異常之前,原本的數據就已經被摧毀了,雖說會拋出異常,程序也會被終止(如果真能檢測到的話,不過就上述代碼似乎沒什麼機會),然而從“異常安全性”的角度來看,這個函數依舊是個非常糟糕的函數(其他問題先不提O(∩_∩)O~)
根據Effective C++ 中所述,“異常安全”需要滿足兩個條件
□ 不泄露任何資源
□ 不允許數據被破壞
而有效的異常安全函數(Exception-safe functions)提供如下三個保證之一:
★ 基本承諾: 如果異常被拋出,程序內的任何事物仍然保持在有效的狀態下。沒有任何對象和數據結構會因該函數調用而被破壞,所有對象都處於一種前後的一致狀態。對於上述賦值函數,如果不對拋出的bad_alloc處理的話,那個這個單鏈表只剩一個空的頭結點,雖然此時會指向一個不存在的結點,但是依舊保留了單鏈表的基本結構。
★ 強烈保證: 如果異常被拋出,程序狀態不改變。調用這樣的函數需要有這樣的認知: 如果函數成功,就是完全成功;如果函數失敗,程序回覆到“調用函數之前的狀態”的狀態。(就像以前打BOSS之前先存個檔,輸了就讀檔一樣)
★ 不拋出保證: 承諾絕不拋出異常,因爲他們總是能夠完成他們原先承諾的功能,比如給new提供一個nothrow保證。
從這裏來看,似乎能夠“讀檔”的強烈保證似乎是一個不錯的保證,因此有必要了解一個有效的方法,這個策略被稱爲copy and swap。簡單來說:爲原本打算修改的對象(原件)作出一份副本,然後在那副本上做一切有必要的修改,若在修改的動作拋出異常,則保持原對象爲改變的狀態。待修改成功後,再將修改過的那個副本和原對象在一個不拋出異常的操作中置換(swap)。
對於copy and swap 策略來說,有兩個重點
①copy
對原本的對象作出一份副本,在已經完成拷貝構造函數的前提下這是極爲輕鬆的,也沒什麼可以多說的。
template <class Temp>
LinkList<Temp>::LinkList(const LinkList ©)
{
LinkNode<Temp> *p = copy.Head->next;
try
{
Head = new LinkNode<Temp>;
LinkNode<Temp> *h = Head;
while (p != NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
}
catch (const bad_alloc & e)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
}
此時我們只需要在賦值函數中調用拷貝構造函數即可完成copy操作
LinkList<Temp> temp(*this);
②swap
對於交換而言可以說的東西確實不少,應該提出一個不拋出異常的swap函數,具體的構造方法詳見 Efficetive c++ 條款25.對於我們的單鏈表,具體的實現如下
template<class Temp>
void LinkList<Temp>::swap(LinkList<Temp> & other)
{
using std::swap;
swap(Head, other.Head);
}
template<typename Temp>
void swap(LinkList<Temp> & a, LinkList<Temp> & b)
{
a.swap(b);
}
該專屬swap與單鏈表類定義在同一個名稱空間內。能夠完成兩個單鏈表對象的置換。
至此我們可以寫成基於copy-and-swap的賦值函數
template <class Temp>
LinkList<Temp> & LinkList<Temp>::operator=(LinkList<Temp> &List)
{
try
{
LinkList<Temp> temp(*this);
temp.DestoryList();
LinkNode<Temp> *p = List.Head;
LinkNode<Temp> *h = temp.Head;
while (p != NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
swap(temp);
return *this;
}
catch (const bad_alloc & e)
{
cerr << "error !" << endl;
return *this;
}
}
“強烈保證”往往都能夠以copy-and-swap實現,但是“強烈保證”並非對所有函數都可以實現。但確實也是一個很有意義的策略!