深入异常处理
有时在工程中只关心是否产生了异常,而不关心具体的异常类型,C++语言可以做到吗?
C++中的catch语句可以使用…捕获所有的异常: catch(...)
#include <cstdlib>
#include <iostream>
using namespace std;
int test(int i)
{
if( i = = 1 )
{
throw "p";//字符串
}
if( i = = 2 )
{
throw 0.5;
}
if( i = = 3 )
{
throw 3;
}
if( i = = 4 )
{
throw 'c';//字符
}
return i;
}
int main(int argc, char *argv[])
{
for(int i=0; i<10; i++)
{
try
{
cout<<test(i)<<endl;//test函数会产生异常,把它放在try语句里面
}
catch(char e)
{
cout<<"Exception: "<<e<<endl;
}
catch(...)
{
cout<<"Exception Occur"<<endl;
}
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
result:
0
Exception Occur
Exception Occur
Exception Occur
Exception: c
5
6
7
8
9
Press the enter key to continue ...
catch(…)可以捕获所有异常但却无法得到异常信息
catch(…)一般作为最后一个异常处理块出现
看见代码中的catch就要意识到这里在处理异常情况,而异常是在对应的try中产生的。
在catch语句块中仍然可以抛出异常
#include <cstdlib>
#include <iostream>
using namespace std;
int test(int i)
{
if( (6 <= i) && (i <= 9) )
{
throw i;
}
return i;
}
int main(int argc, char *argv[])
{
try
{
for(int i=0; i<10; i++)
{
try
{
cout<<test(i)<<endl;//for循环,正常打印0-5;i=6时扔出来一个异常
}
catch(int e)//i=6的异常被他接住
{
cout<<"Exception: "<<e<<endl;
throw e;//catch里又产生异常,找try,扔出去,再找try对应的catch
//扔出的异常没有被捕获,程序就会死掉
}
}
}
catch(int e)//捕获!接住catch里的异常
{
cout<<"Catch: "<<e<<endl;
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
result:
0
1
2
3
4
5
Exception: 6
Catch: 6
Press the enter key to continue ...
在catch(…)语句块中,可以通过不带参数的throw语句抛出捕获的异常
#include <cstdlib>
#include <iostream>
using namespace std;
int test(int i)
{
if( (6 <= i) && (i <= 9) )
{
throw i;
}
return i;
}
int main(int argc, char *argv[])
{
try
{
for(int i=0; i<10; i++)
{
try
{
cout<<test(i)<<endl;
}
catch(...)
{
cout<<"Exception Occur"<<endl;
throw;//扔出外层catch所捕获到的异常
}
}
}
catch(int e)//try捕获到的异常是整型6,正好匹配
{
cout<<"Catch: "<<e<<endl;
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
result:
0
1
2
3
4
5
Exception Occur
Catch: 6
Press the enter key to continue ...
异常与对象
不要在构造函数中抛出异常
在构造函数可能申请系统资源,而在构造函数中抛出异常会导致对象构造不完全(构造函数还没结束,不给你调用析构函数)
不完全对象的析构函数是不会被调用的,因此可能造成资源泄漏
构造函数中的异常示例(构造函数不完全,导致未调用析构函数👇)
#include <cstdlib>
#include <iostream>
using namespace std;
class Test
{
int* p;
public:
Test()
{
cout<<"Test()"<<endl;
p = new int[5];//创建一个包含5个整数的数组,并将该数组的首地址赋值给指针p。
//delete[] p; // 正确释放数组内存
//p = new int(5);//创建一个单一的整数对象,并将其初始化为值5,
//delete p; // 正确释放单个对象的内存
throw 10;
}
~Test()
{
cout<<"~Test()"<<endl;
delete[] p;
//没调用,内存没释放,导致内存报废,内存泄露
}
};
int main(int argc, char *argv[])
{
try
{
Test t;
}
catch(int e)
{
cout<<"Catch: "<<e<<endl;
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
result:
Test()
Catch: 10
Press the enter key to continue ...
工程中的异常应用
-
在工程中会定义一系列的异常类(工程中抛出异常类对象)
-
通过继承,可以得到一个异常类族
-
每个类代表工程中可能出现的一种异常类型
-
由于对象构造与拷贝的开销,在定义catch语句块时使用引用作为参数
标准库中的异常
-
在工程中可以使用标准库中的异常类
-
可将标准库中的异常类作为基类派生新的异常类
-
标准库中的异常都是从exception类派生的
-
exception类有两个主要的分支
-
logic_error用于描述程序中出现的逻辑错误
-
如:传递无效参数
-
-
runtime_error用于描述无法预料的事件所造成的错误
-
如: 内存耗尽,硬件错误
-
-
logic_error和runtime_error都提供了一个参数为字符串的构造函数,这样就可以保持错误信息
通过what()成员函数就可以得到错误的信息
#include <cstdlib>
#include <iostream>
#include <stdexcept>//异常头文件
using namespace std;
class divide_by_zero : public logic_error
{
public:
divide_by_zero(const char* s) : logic_error(s)//初始化列表
{
}
};
double Div(double a, double b)
{
if( (-0.00000001 < b) && ( b < 0.00000001) )
{
throw divide_by_zero("Divide by zero...");//抛出一个异常类
}
return a / b;
}
int main(int argc, char *argv[])
{
try
{
cout<<Div(1, 0)<<endl;
}
catch(exception& e)//用引用,不发生拷贝
//exception是父类,根据面向对象复制兼容性原则,
//这里可以不用改成catch(divide_by_zero& e)
//如果异常在异常类族里面,那么通过这个catch,就可以捕获所有异常
{
cout<<e.what()<<endl;
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
result:
Divide by zero...
Press the enter key to continue ...
函数级try语法
可以将函数体作为一个完整的try语句块
函数级try语法可以更好将正常逻辑代码与异常处理代码分开,提高代码的可读性与维护性。
#include <cstdlib>
#include <iostream>
#include <stdexcept>
using namespace std;
int func1(int i)//花括号太多,等价于下边的
{
try
{
if( i > 0 )
{
return i;
}
else
{
throw "error";
}
}
catch(...)
{
return -1;
}
}
int func2(int i) try//这个函数是会产生异常的,产生的异常由后边的catch去捕获
{
if( i > 0 )
{
return i;
}
else
{
throw "error";
}
}
catch(...)
{
return -1;
}
int main(int argc, char *argv[])
{
for(int i=0; i<5; i++)
{
cout<<func2(i)<<endl;
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
result:
-1
1
2
3
4
Press the enter key to continue ...