copy-and-swap改进"异常安全"

来源:互联网 发布:淘宝开充话费店的知识 编辑:程序博客网 时间:2024/05/22 18:22

在了解到异常安全的重要性的重要性后,马上想到自己在刚学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实现,但是“强烈保证”并非对所有函数都可以实现。但确实也是一个很有意义的策略!

0 0