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