copy-and-swap改進"異常安全"

在瞭解到異常安全的重要性的重要性後,馬上想到自己在剛學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 &copy)
    {

        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實現,但是“強烈保證”並非對所有函數都可以實現。但確實也是一個很有意義的策略!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章