努力写异常安全的代码
# 为什么绕不开异常
如果是从c转型到c++的,其实对异常会非常不适应,开发的代码往往是不考虑异常,异常来了,一路上行,不会被catch,直到系统硼溃。
如果用c++那么绕不开异常,new, delete, stl的容器都可能抛出异常。最多是你对所有的异常都不处理,但是异常已经在c++程序中不可避免了。
异常的目的。
当异常发生的时候,如果你捕获到异常,那么程序还能从补货点开始执行。如果一直没有被捕获到,那程序就会跟没有异常机制一样崩溃。
但是要用好这个特性很难很难,你要保证try区域的代码用到的所有调用都是异常安全的,才能让程序在异常发生后,还能像没事一样继续运行。
# 尽量保证函数异常安全
这里举一个例子来说明异常安全在代码实现过程中的考虑。
```
class Menu{
public:
void changeSrc(const string imgName);
private:
Mutex mutex;
Image *srcImg;
int imgCnt;
};
void Menu::changeSrc(const string& imgName) throw()
{
lock(&mutex);
delete srcImg;
imgCnt++;
srcImg = new Image(imgSrc);
unlock(&mutex);
```
上面的changeSrc函数实现存在了很多问题,首先他声称throw(), 表明自己不抛异常,实际上delete 和 new 都是有可能抛出异常的。函数末尾的throw() 只是君子约定,不作数,他是否真实的不抛异常,不看这个声明,而是要看他的具体实现。所以throw()几乎没有参考意义。除非你很信任这个函数的开发者,坚信对方说throw()就一定不会抛异常。
在上面这个函数中至少存在2个主要问题,1)如果delete或者new异常,会导致,lock资源泄露。 解决办法是要把lock封装在对象中。2)这个函数的代码顺序是先释放了srcImg 然后再为srcImg New 新的资源。那问题是如果new异常,会导致srcImg变成野指针。这里的解决方法有多种,一是利用copy and swap技术,一种是利用智能指针的reset。
先说下如何利用智能指针。 在类里面不维护Image*类型的数据, 而是维护shared_ptr<Image> 类型的智能指针对象
```
class Menu{
public:
void changeSrc(const string imgName);
private:
Mutex mutex;
shared_ptr<Image> smart_srcImg;
int imgCnt;
};
void Menu::changeSrc(const string& imgName) throw()
{
lock_gard<std::mutex> mtxGard(mutex);
smart_srcImg.reset(new Image(imgName));
imgCnt++;
}
```
利用智能指针的reset,如果new异常了,那么就的智能指针指向的资源还不会释放。只有new成功了,reset函数才会释放旧的资源。
接下来说明如何利用copy and swap思想手动解决问题代码,见注释
```
void Menu::changeSrc(const string& imgName) throw()
{
lock_gard<std::mutex> mtxGard(mutex);
//delete srcImg; 先不要着急修改原有的对象,而应该,把原有对象拷贝生成一份临时对象。Image* tmp = srcImg; tmp =new Image(imgSrc);
//保证临时对象需要修改的东西已经修改完毕,需要的新资源已经new成功后。此后不会发生异常了,然后把旧的对象和这个临时对象swap,然后删除旧对象的关联的资源
swap(srcImg, tmp);
//离开作用域后tmp会自动释放资源,当然这里的例子做不到。因为tmp仅仅只是指针而已。他做不到自动释放关联的资源。这里仅仅示意copy and swap的流程。
imgCnt++;
```
网友评论