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

本文探讨了C++中异常安全的重要性,并详细介绍了如何通过copy-and-swap策略来实现基本承诺和强烈保证的异常安全。该策略涉及复制原始对象、在副本上进行修改,如果操作过程中抛出异常,则保持原始对象不变。文章还强调了实现不抛出异常的swap函数的关键作用,并提供了单链表中应用copy-and-swap的示例。
摘要由CSDN通过智能技术生成

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值