异常安全性问题

异常的安全性

在程序没有发生异常的情况下能够正常运转的代码好写,但是在发生异常后,也能够保证程序不崩溃,成本就高一些。而编写异常安全的函数,即函数运行过程中,不产生异常,则是最高要求。
如《C++语言程序设计(第四版)》中提到的入栈函数

template<class T,int SIZE>
void Stack<T, SIZE>::push(const T&item)
{
	assert(!isFull());
	list[++top] = item;
}

如果T是个复杂的数据类型,在赋值运算的时候,赋值运算符可能会被重载,在这过程中可能会抛出异常。若抛出,则在这种情况下,top+1已完成,但是没有被成功赋值。若此时pop弹栈,则弹栈的值将会不确定。
这个函数若改为

template<class T,int SIZE>
void Stack<T, SIZE>::push(const T&item)
{
	assert(!isFull());
	list[top+1]=item;
	top++;
}

若在赋值的时候抛出异常,则保证在异常前后,栈保持不变,不会造成不可预知的后果。

异常安全编程基本原则

  1. 明确哪些操作绝对不会抛出异常

C++保证基本数据类型的绝大部分操作(除0操作除外),如指针赋值、算术运算、比较都不会抛出异常。STL的很多容器和算法,在模版相关操作不抛出异常的情况下也不会抛出异常。
其中STL的各个容器的swap()函数绝对不会抛出异常。

  1. 确保析构函数不抛出异常

在程序用,一旦有异常抛出,在抛出前,则会解旋,依次将异常节点前的对象析构。如果析构过程中再次抛出异常,则异常处理机制就无法继续工作,导致terminate函数被调用,程序中止。

  1. 避免异常发生时的资源泄漏

资源,包括动态内存、文件、互斥锁、条件变量、套接字、管道等,这里以动态内存为例。

动态内存的分配应该是编写程序时经常会用到的。我们知道,在new申请了一块堆上内存的时候,需要在函数结束前将其delete掉。如果考虑到异常机制,则需要在throw异常前,将申请的内存释放。

void fun(int n)
{
	int *s = new int[n];		//申请动态内存

	if (...)
	{
		delete []s;				//达成某条件后抛出异常,在此之前释放内存
		throw exception();
	}

	try
	{
		otherFun();				//执行某一可能抛出异常的函数
	}
	catch (...)
	{
		delete[]s;				//异常处理前释放内存
		throw;					//本层无法处理,向上抛出异常
	}

	delete[]s;					//程序若正常结束,则在结束前抛出异常
}

可以看到,在各个可能抛出异常,导致函数无法正常结束的地方,都需要增加一个delete操作,在函数较为复杂的情况下,这个工作量很大而且一旦忘记某一个地方,就有可能造成内存泄漏。

在堆上分配的动态内存需要如此,好在在栈上分配的临时变量会自动析构。如此,就有个捷径可走,即将动态内存分配封装一下,编程临时变量。而STL的vector则替我们完成了这一工作。

void fun(int n)
{
	vector<int>s(n)				//申请动态内存

	if (...)
	{		
		throw exception();
	}
	otherFun();				//执行某一可能抛出异常的函数	
}

这样,无论是在if语句内抛出异常,还是otherFun()抛出的异常,都直接由调用方处理,而s则自动析构。就会方便很多。

那么问题又来了,如果一定要用new动态分配呢,毕竟比栈上的对象灵活。相信大家心中已经有了答案,智能指针。这个问题单独再说。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值