什么时候引发异常?
当程序在执行过程中出现了我们不希望的看到的情况,比如除数为0,用户输入的年龄为负数,new新对象时内存空间不足无法分配,数组越界等等。。。
发生异常时,可能需要用户重新输入,也可能会导致系统崩溃,在结束前我们要做一些必要工作,如将内存中的数据存进磁盘,关闭打开的文件,释放动态分配的内存等。。。
c++通过throw抛出异常,使用try包裹可能发生异常的语句,使用catch捕获throw抛出的异常,并进行处理。
void func1(int age1, int age2){
if(age1<0 or age2<0){
throw invalid_argument("age not able less than zero");
}
if(age1+age2 > 200){
throw runtime_error("live time too long!");
}
}
void func2(){
...
try{
func1(-1,10);
}catch(invalid_argument& e){
cout << e.what() << endl;
cout << "Did you want try again? Entry y or n";
}
...
}
void func3(){
...
try{
func2();
}catch(runtime_error& e){
cout << e.what() << endl;
cout << "dead";
}
...
}
异常的处理规则
throw抛出的异常类型与catch抓取的异常类型要一致;
throw抛出的异常类型可以是子类对象,catch可以是父类对象;
catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常捕获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获;
如果使用catch参数中,使用基类捕获派生类对象,一定要使用传递引用的方式,例如catch (exception &e);
异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码;
被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个;
在try的语句块内声明的变量在外部是不可以访问的,即使是在catch子句内也不可以访问;
栈展开会沿着嵌套函数的调用链不断查找,直到找到了已抛出的异常匹配的catch子句。如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止。
C++的标准异常
- exception头文件定义了最通用的异常类exception。他只报告异常的产生,不提供额外的信息。
- stdexcept头文件定义了几种常用的异常类
name | descrept |
---|---|
exception | 最常见的问题 |
runtime_error | 只有在程序运行过程中才能检测出的问题 |
range_error | 运行时错误:生成的结果超出了有意义的值的范围 |
overflow_error | 运行时错误:计算上溢 |
underflow_error | 运行时错误:计算下溢 |
logic_error | 程序逻辑错误 |
domain_error | 逻辑错误:参数对应的结果值不存在 |
invalid_argument | 逻辑错误:无效参数 |
length_error | 逻辑错误:试图创建一个超过该类型最大长度的对象 |
out_of_range | 逻辑错误:使用一个超出有效范围的值 |
- new头文件定义了bad_alloc异常类型。
- type_info头文件定义了bad_cast异常类型。
标准库异常类只定义了几种运算,包括创建或拷贝异常类型的对象,以及为异常类型的对象赋值。
我们只能以默认初始化的方式初始化exception、bad_cast、bad_alloc对象,不允许为这些对象提供初始值。
其他异常类型相反,必须使用字符串初始化。
异常类定义了一个what成员函数,无参,返回值时const char*类型。
可以通过继承和重载 exception 类来定义新的异常。
#include <iostream>
#include <exception>
using namespace std;
struct MyException : public exception{
const char* what() const throw (){
return "C++ Exception";
}
};
int main(){
try{
throw MyException();
}catch(MyException& e) {
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
} catch(std::exception& e) {
//其他的错误
}
}
// result:
// MyException caught
// C++ Exception
总结
- 使用异常处理的优点:
传统错误处理技术,检查到一个错误,只会返回退出码或者终止程序等等,我们只知道有错误,但不能更清楚知道是哪种错误。使用异常,把错误和处理分开来,由库函数抛出异常,由调用者捕获这个异常,调用者就可以知道程序函数库调用出现的错误是什么错误,并去处理,而是否终止程序就把握在调用者手里了。
- 使用异常的缺点:
如果使用异常,光凭查看代码是很难评估程序的控制流:函数返回点可能在你意料之外,这就导致了代码管理和调试的困难。启动异常使得生成的二进制文件体积变大,延长了编译时间,还可能会增加地址空间的压力。
C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。 这个需要使用RAII来处理资源的管理问题。学习成本较高。
C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
- 什么时候使用异常?
建议:除非已有的项目或底层库中使用了异常,要不然尽量不要使用异常,虽然提供了方便,但是开销也大。
- 程序所有的异常都可以catch到吗?
并非如此,只有发生异常,并且又抛出异常的情况才能被 catch 到。例如,数组下标访问越界的情况,系统是不会自身抛出异常的,所以我们无论怎么 catch 都是无效的;在这种情况,我们需要自定义抛出类型,判断数组下标是否越界,然后再根据自身需要throw自定义异常对象,这样才可以catch到异常,并进行进一步处理。