C++ 11try catch 异常处理
1、异常处理
异常处理(exception handing)机制可以将问题的检测和问题的解决过程分离开。程序的一部分复制检测问题的出现,然后解决问题的任务传递给程序的另外一部分。
2、异常抛出
程序通过抛出(throw)一条表达式来引发一个异常。被抛出的表达式类型及当前的调用链共同决定了那一段处理代码将被用来处理该异常。
抛出异常后的代码逻辑:
1、当执行一个throw时,跟在throw后面的语句将不被执行。
2、程序控制权转移到函数的局部catch块处理,或者是直接/间接调用发生异常函数的catch块中处理。
因此将产生2个问题。
1、抛出异常的函数,在异常之后的代码将不会执行。
2、沿着调用链创建的对象将被销毁。(栈展开)
2.1、提前结束函数
#include <iostream>
using namespace std;
void test_func(){
cout << "test_func throw exception begin" << endl;
__throw_invalid_argument("test invalid argument");
cout << "test_func throw exception end" << endl; //此打印不会执行
}
int main()
{
try
{
test_func();
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
}
return 0;
}
执行结果:
提前结束函数会带来很多的后续处理代码,例如下面2方面:
1、对于业务:正常的业务流程终端,如何保证业务的事务性。
2、对于资源:比如打开的文件描述fd需要关闭,malloc申请的内存需要释放等工作。
2.2、栈展开
异常发生后,throw抛出异常表达式类型需要找到能够处理该类型异常的catch block进行处理,如果当前函数没有找到相应的catch分支块,着沿着调用链向上匹配,直到找到匹配的catch block。
实例中test_func3函数发生的异常,直到test_func函数才找到对应的异常处理模块。
并且所有函数的call test_funcx end都没有打印。
void test_func(){
try{
cout << "call test_func1" << endl;
test_func1();
cout << "====call test_func1 end====" << endl;
}
catch(const std::invalid_argument& e){
std::cerr << e.what() << '\n';
}
}
void test_func1(){
cout << "call test_func2" << endl;
test_func2();
cout << "===call test_func2 end===" << endl;
}
void test_func2(){
try{
cout << "call test_func3" << endl;
test_func3();
cout << "===call test_func3 end===" << endl;
}catch(const std::overflow_error & e){
std::cerr << e.what() << '\n';
}
}
void test_func3(){
try{
cout << "throw invalid_argument exception" << endl;
__throw_invalid_argument("test invalid argument");
cout << "throw invalid_argument exception end" << endl;
}catch(const std::bad_alloc& e){
std::cerr << e.what() << '\n';
}
}
int main()
{
cout << "call test_func" << endl;
test_func();
cout << "=call test_func end=" << endl;
return 0;
}
执行结果:
=call test_func=
call test_func1
call test_func2
call test_func3
throw invalid_argument exception
test invalid argument
=call test_func end=
2.3、未捕获异常
上面示例中异常在最后都找到了捕获的catch分支。如果异常没有匹配到catch分支会怎么样?
程序将使用terminate结束程序
#include <iostream>
using namespace std;
void test_func1();
void test_func2();
void test_func3();
void test_func(){
cout << "call test_func1" << endl;
test_func1();
cout << "====call test_func1 end====" << endl;
}
void test_func1(){
cout << "call test_func2" << endl;
test_func2();
cout << "===call test_func2 end===" << endl;
}
void test_func2(){
try{
cout << "call test_func3" << endl;
test_func3();
cout << "===call test_func3 end===" << endl;
}catch(const std::overflow_error & e){
std::cerr << e.what() << '\n';
}
}
void test_func3(){
try{
cout << "throw invalid_argument exception" << endl;
__throw_invalid_argument("test invalid argument");
cout << "throw invalid_argument exception end" << endl;
}catch(const std::bad_alloc& e){
std::cerr << e.what() << '\n';
}
}
class A {
public:
A(){};
~A(){}
};
int main()
{
cout << "call test_func" << endl;
test_func();
cout << "=call test_func end=" << endl;
return 0;
}
代码执行结果:
terminate called after throwing an instance of 'std::invalid_argument'
what(): test invalid argument
Aborted (core dumped)
3、资源回收
上面说过函数退出时,动态申请的资源需要在catch分支中进行手动回收。
而栈上的临时对象是系统自动回收。
3.1、临时对象回收
我们都知道临时class对象是通过析构函数回收的。如果析构函数发生异常会发生什么情况?
一个未捕获异常还没有处理,又发生一个异常,程序崩溃。
#include <iostream>
using namespace std;
void test_func1();
void test_func2();
void test_func3();
class A {
public:
A(){cout << "A constructor call" << endl;};
//析构函数默认是noexcept(true),如果抛出异常直接会调用terminate结束程序
//所以这里析构函数声明为noexcept(false)
~A() noexcept(false);
};
A::~A()noexcept(false){
cout << "A destructor call" << endl;
__throw_domain_error("throw domain error");
};
void test_func(){
try{
cout << "call test_func1" << endl;
test_func1();
cout << "====call test_func1 end====" << endl;
}catch(const std::exception &e){
std::cerr << e.what() << '\n';
}
}
void test_func1(){
cout << "call test_func2" << endl;
test_func2();
cout << "===call test_func2 end===" << endl;
}
void test_func2(){
try{
cout << "call test_func3" << endl;
//这里声明临时变量A,异常处理时A的析构函数调用。
//析构函数抛出异常导致程序奔溃
A a;
test_func3();
cout << "===call test_func3 end===" << endl;
}catch(const std::overflow_error & e){
std::cerr << e.what() << '\n';
}
}
void test_func3(){
try{
cout << "throw invalid_argument exception" << endl;
__throw_invalid_argument("test invalid argument");
cout << "throw invalid_argument exception end" << endl;
}catch(const std::bad_alloc& e){
std::cerr << e.what() << '\n';
}
}
int main()
{
cout << "call test_func" << endl;
test_func();
cout << "=call test_func end=" << endl;
return 0;
}
执行结果:
call test_func
call test_func1
call test_func2
call test_func3
A constructor call
throw invalid_argument exception
A destructor call
terminate called after throwing an instance of 'std::domain_error'
what(): throw domain error
Aborted (core dumped)
3.2、析构函数默认是noexcept
析构函数默认是noexcept(true)不对外抛出异常,即必须在析构函数内部处理掉所有异常。(noexcept 可以看我的博客c++11 noexecpt)
#include <iostream>
using namespace std;
void test_func1();
void test_func2();
void test_func3();
class A {
public:
A(){cout << "A constructor call" << endl;};
~A();
};
A::~A(){
cout << "A destructor call" << endl;
__throw_domain_error("throw domain error");
};
int main()
{
try
{
A a;
}
catch(const std::exception& e) //即使这里捕获异常,仍然崩溃就是默认析构函数noexcept
{
std::cerr << e.what() << '\n';
}
return 0;
}
执行结果:
A constructor call
A destructor call
terminate called after throwing an instance of 'std::domain_error'
what(): throw domain error
Aborted (core dumped)
4、构造函数初始值的异常
程序执行的任何时刻都可能发生异常,如果构造函数在执行初始值列表是发生异常,而此构造函数体内的try语句块还未生效,所以无法处理构造函数初始值列表抛出的异常。
要想处理此种异常,代码应该如下处理:
class A{
public:
A(int i) try : m_i(i) {cout << "A constructor call" << endl;}catch(...){};
~A();
int m_i;
};
A::~A(){
cout << "A destructor call" << endl;
__throw_domain_error("throw domain error");
};
5、异常的类层次
可以通过这些类来实现我们自定义的异常处理类型。