Essential c++ 第七章
第七章主要介绍了如何使用异常来使你的程序更安全。
文章目录
1.异常的抛出
异常抛出使用throw关键词进行,当函数中出现某个错误后,会抛出某个标志物。这个标志物可以是简单的值类型,也可以是复杂的符合类型。注意,当程序运行throw时,其后面的程序都会终止进行。比如本例子中,如果num大于max,最后一句创建成功是不会被运行的。
抛出int
//例一 抛出int
void f1(int num, int max = 50)
{
if (num > max)
throw 42;
cout << "创建成功" << endl;
}
抛出string,注意,catch解析这个错误的时候,使用const char*类型接受
//例二 抛出string
void f2(int num, int max = 50)
{
if (num > max)
throw "输入越界";
cout << "创建成功" << endl;
}
抛出class有两种写法,一种是不抛出具体实例,一种是建立实例以后再抛出实例
//例三 抛出class的两种写法
class error
{
public:
error(int num,int max):_num(num),_max(max){}
void what()
{
cout << _num << "超过了最大值" << _max << endl;
}
private:
int _num;
int _max;
};
void f3(int num, int max = 50)
{
if (num > max)
{
throw error(num, max);
}
cout << "创建成功" << endl;
}
void f4(int num, int max = 50)
{
if (num > max)
{
error err(num, max);
throw err;
}
cout << "创建成功" << endl;
}
2.异常的处理
异常的处理使用try-catch语句进行。try-catch-throw是一组常用的异常处理关键词。通过try中运行可能出错的程序,throw抛出异常,catch接受throw抛出的异常,从而进行处理。我们针对上面的四种错误抛出,定义四种try-catch语句。当try语句某句话抛出异常时,这个语句及其后面的程序都将不再进行。
2.1 try-catch
2.1.1 捕获属于catch的异常
当try中抛出异常的时候,下面的catch会进行一一匹配,找到抛出的异常类型,然后运行相应的catch中的程序。
处理抛出的int
//01 例程1
cout << "抛出int" << endl;
try
{
f1(100, 50);
}
catch (int len)
{
cout << len << endl;
}
cout << endl;
处理抛出的string
//02 例程2
cout << "抛出string" << endl;
try
{
f2(100, 50);
}
catch (const char* str)
{
cout << str << endl;
}
cout << endl;
处理抛出的error类
//03 例程3-1
cout << "抛出class1" << endl;
try
{
f3(100, 50);
}
catch ( error& err)
{
err.what();
}
cout << endl;
//03 例程3-2
cout << "抛出class2" << endl;
try
{
f4(100, 50);
}
catch (error & err)
{
err.what();
}
cout << endl;
2.1.2 在父类中查找异常种类
当然也会存在抛出的异常不在catch中的情况,这个时候,catch会检测,被抛出的异常的父类是否能够与自己匹配,如果能,就会执行相应的catch
我们定义相应的异常类和抛出异常的函数
// 从父类中寻找异常类型
class error1:public error
{
error1(int num,int max):error(num,max){}
};
void f5(int num, int max = 50)
{
if (num > max)
{
error err1(num, max);
throw err1;
}
cout << "创建成功" << endl;
}
使用catch捕获异常,不过函数抛出的是error1,而catch中写的是error,但是仍然能够正确运行,就是因为catch发现没有可以匹配的异常类型,但是发现error1的父类就行error,所以接受error异常类型的catch,就能够接受error1异常类型,然后执行catch
//01 从父类寻找异常类型
cout << "从父类寻找异常类型" << endl;
try
{
f5(100, 50);
}
catch (error & err)
{
err.what();
}
cout << endl;
2.1.3 捕获全部异常
还有一种写法,能够捕获所有的异常,就是catch(…)
try
{
//something
}
catch(...)
{
//捕获所有异常
}
举例
cout << "注意事项一,catch不能有包含关系" << endl;
try
{
f4(100, 50);
}
catch (...)
{
cout << "error!" << endl;
}
cout << endl;
catch (error & err)
{
err.what();
}
2.1.4 注意
- catch语句不能有相互包含
如果catch语句相互包含,程序会运行错误,因为不知道该使用哪个catch了,比如我们把catch(…)放到前面,这个时候,后面的catch语句无论如何也不能执行,就被屏蔽掉了,程序是无法执行的。
cout << "catch(...)全部抓住" << endl;
try
{
f4(100, 50);
}
catch (...)
{
cout << "error!" << endl;
}
cout << endl;
- 构造函数不能抛出异常
也不能在构造函数中抛出异常,尤其是构造函数中使用new的时候。因为,构造函数如果抛出异常,意味着对象没有定义成功,但是内存以及被分配了,对象没定义,意味着不会执行析构函数,所以会造成内存泄露.
2.2 throw
如果当前层级的catch不能很好的处理异常,可以使用throw,将异常往上一级抛出,让上一级的catch函数进行处理。只写一个throw;即可,不需要重复再写throw的内容。而且,只有在catch中,才可以使用throw继续往上一层抛出异常
2.3 沿着函数调用链上传
如果这个函数抛出异常的时候,这个位置没有catch函数,异常会沿着调用他的函数继续往上抛出,一直到有一层有了try-catch语句。不过,如果异常抛出到main层,还不能被处理,程序会被强制终止。
void level1()
{
throw 1;
}
void level2()
{
level1();
}
int main()
{
cout << "沿着调用链上传异常" << endl;
try
{
level2();
}
catch (int len)
{
cout << len << endl;
}
cout << endl;
return 0;
}
2.4 异常的处理流程
编译器对try中抛出异常的综合的处理过程是
- 先检查catch中是否有符号的异常类型。
- 如果没有,检查他的父类有没有这种异常类型,如果有进行处理
- 如果都没有会把异常向他的调用者抛出
- 如果一直到main函数都没有人能够处理这种异常,那么程序会被终止。
3.标准异常
除了我们自己定义的异常以外,c++还有自己内置的异常类,这些异常被称为标准异常。
3.1 举例 bad_alloc
比如bad_alloc会在new不能提供足够内存大小的时候被抛出,这种异常一般定义在标准函数库中。
3.2 抽象类exception
所有的标准库异常都来源于抽象类exception,exception类中有一个叫做what的虚函数,返回值为const char*,用来记录具体是哪个标准异常被抛出。
3.3 通过继承exception定义自己的异常
我们自定义异常的时候,可以继承exception类作为父类,这样的话,catch中写exception,即可以捕获所有类型的异常,并且能够找出他们的名字。如果想要继承exception,注意两点
- 必须重载虚函数what
- 必须包括头文件exception
这里要提两个技巧,一个是如何把多种混杂类型的串变成字符串,另外一个是如何把string变成const char*类型
继承标准异常库定义自己的异常
class exceptionError:public exception
{
public:
exceptionError(int num,int max):_num(num),_max(max){}
const char* what()
{
ostringstream ex_msg;
static string msg;
//技巧一 把混合数据类型的串变成字符串
ex_msg << _num << "超过最大值" << _max << "!" << endl;
msg = ex_msg.str();
//技巧二 把字符串变成 const char *
return msg.c_str();
}
private:
int _num;
int _max;
};
void throwExceptionError(int num,int max=50)
{
if (num > max)
{
throw exceptionError(num, max);
}
cout << "创建成功" << endl;
}
使用自己定义的异常
cout << "继承标准异常库" << endl;
try
{
throwExceptionError(100, 50);
}
catch (exception & exp)
{
exp.what();
}
cout << endl;
4. 局部资源管理
下面这个例子,不妨从异常的角度来看看哪里错了
extern Mutex m;
void f()
{
int *p = new int ;
m.acquire();
process(p);
m.release();
delete p;
}
看似这个函数很正常,但是一旦process函数抛出了异常,后面的内存释放函数就不能运行了,就会造成内存泄露,如何避免这个问题呢
4.1 使用try语句
可以用try-catch语句来避免内存泄露
extern Mutex m;
void f()
{
try
{
int *p = new int ;
m.acquire();
process(p);
m.release();
delete p;
}
catch(...)
{
m.release();
delete p;
}
}
这样虽然是可行的,但是这么写,未免太啰嗦了,其实还有其他的解决方案
4.2 在初始化阶段进行资源的请求
我们可以把内存类型的变量定义在类的构造函数中,这样即使中间的process函数抛出了异常,也能够通过析构函数释放内存。因为析构函数保证,会在抛出异常结束程序运行之间执行完毕,能够保证不发生内存泄露
void f()
{
auto_ptr<int> p(new int);
MutexLock m1(m);
process();
//其他什么都不用写了,如果process抛出异常,析构函数会悄悄的执行
}