1. try catch异常语法
1.1. tray catch与throw
C++内置异常处理语法try...catch...,try总是与catch一同出现,伴随一个try语句,至少应该有一个catch()语句。try内部正常应用逻辑,正常逻辑内部可能会有throw抛出异常。catch带有一个参数,参数类型以及参数名字都由程序指定,名字可以忽略,如果在catch随后的block中并不打算引用这个异常对象的话。参数类型可以是build-intype,例如int,long, char等,也可以是一个对象,一个对象指针或者引用。如果希望捕获任意类型的异常,可以使用“...”作为catch的参数。
throw后面带一个类型的实例,它和catch的关系就象是函数调用,catch指定形参,throw给出实参。编译器按照catch出现的顺序以及catch指定的参数类型确定一个异常应该由哪个catch来处理。throw不一定非要出现在try随后的block中,它可以出现在任何需要的地方,只要最终有catch可以捕获它即可。即使在catch随后的block中,仍然可以继续throw。这时候有两种情况,一是throw一个新类型的异常,这与普通的throw一样。二是要rethrow当前这个异常,在这种情况下,throw不带参数即可表达。例如:
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
void Demo1()
{
double val = 1.0;
try
{
throw 'c';
}
catch(char c)
{
cout << "catch(char c)" << endl;
throw val; // 代码抛出新类型异常
}
catch(short c)
{
cout << "catch(short c)" << endl;
throw; // 代码重新抛出当前的异常
}
catch(double c)
{
cout << "catch(double c)" << endl;
}
catch(...)
{
cout << "catch(...)" << endl;
}
}
void Demo2()
{
throw string("D.T.Software");
}
int main(int argc, char** argv) {
try{
Demo1();
}catch(double c){
std::cout << "Catch throw double " << c << std::endl;
}
try
{
Demo2();
}
catch(char* s)
{
cout << "catch(char *s)" << endl;
}
catch(const char* cs)
{
cout << "catch(const char *cs)" << endl;
}
catch(string ss)
{
cout << "catch(string ss)" << endl;
}
return 0;
}
catch异常匹配处理规则是严格从上到下优先匹配的,但catch不一定要全部捕获tryblock中抛出的异常,剩下没有捕获的可以交给上一级函数处理。当然还有一种情况是我们再catch语句中处理完内容以后再次调用throw抛出异常,这个抛出的异常只能在本方法调用的函数中进一步被捕捉到。什么情况下面会有这种异常重新抛出异常的情况呢?一个较大的项目必然会包含很多第三方的库文件,这个时候我们项目中为了异常的统一性,自然会进行一次异常的分装处理,这边是一种很好的异常捕获中抛出异常的情形。
#include <iostream>
#include <string>
using namespace std;
class Base
{
};
class Exception : public Base
{
int m_id;
string m_desc;
public:
Exception(int id, string desc)
{
m_id = id;
m_desc = desc;
}
int id() const
{
return m_id;
}
string description() const
{
return m_desc;
}
};
/*
假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码
函数名: void func(int i)
抛出异常的类型: int
-1 ==》 参数异常
-2 ==》 运行异常
-3 ==》 超时异常
*/
void func(int i)
{
if( i < 0 )
{
throw -1;
}
if( i > 100 )
{
throw -2;
}
if( i == 11 )
{
throw -3;
}
cout << "Run func..." << endl;
}
void MyFunc(int i)
{
try
{
func(i);
}
catch(int i)
{
switch(i)
{
case -1:
throw Exception(-1, "Invalid Parameter");
break;
case -2:
throw Exception(-2, "Runtime Exception");
break;
case -3:
throw Exception(-3, "Timeout Exception");
break;
}
}
}
int main(int argc, char *argv[])
{
try
{
MyFunc(11);
}
catch(const Exception& e)
{
cout << "Exception Info: " << endl;
cout << " ID: " << e.id() << endl;
cout << " Description: " << e.description() << endl;
}
catch(const Base& e)
{
cout << "catch(const Base& e)" << endl;
}
return 0;
}
2.4函数声明
函数声明异常这种方式在当前代码项目中很少被使用,其使用方法是还有一个地方与throw关键字有关,就是函数声明。例如:
void foo() throw (int); //只能抛出int型异常
voidbar() throw (); //不抛出任何异常
voidbaz(); // 可以抛出任意类型的异常或者不抛出异常
如果一个函数的声明中带有throw限定符,则在函数体中也必须同样出现:
void foo() throw (int)
{
...
}
这里有一个问题,非常隐蔽,就是即使你象上面一样编写了foo()函数,指定它只能抛出int异常,而实际上它还是可能抛出其他类型的异常而不被编译器发现:
void foo() throw (int)
{
throw float; // 错误!异常类型错误!会被编译器指出
...
baz(); // 正确!baz()可能抛出非int异常而编译器又不能发现!
}
voidbaz()
{
throw float;
}
这种情况的直接后果就是如果baz()抛出了异常,而调用foo()的代码又严格遵守foo()的声明来编写,那么程序将abort()。这曾经让我很恼火,认为这种机制形同虚设,但是还是有些解决的办法,请参照“使用技巧”中相关的问题。
如果函数定义的异常和实际抛出的异常不一致的情况下,这个时候unexcepted函数函数会被自动调用。我们可以通过自定unexcepted函数进行这规划总异常类型函数的捕捉。
#include <iostream>
#include <cstdlib>
#include <exception>
using namespace std;
void my_unexpected()
{
cout << "void my_unexpected()" << endl;
// exit(1);
throw 1;
}
void func() throw(int)
{
cout << "func()";
cout << endl;
throw 'c';
}
int main()
{
set_unexpected(my_unexpected);
try
{
func();
}
catch(int)
{
cout << "catch(int)";
cout << endl;
}
catch(char)
{
cout << "catch(char)";
cout << endl;
}
return 0;
}
1. 异常使用技巧
关于标注库中的异常类分析,详细见。
1.1 异常是如何工作的
为了可以有把握的使用异常,我们先来看看异常处理是如何工作的。
1.2. 异常的扩充
2. 异常注意
问题:如果异常不处理,最后会被传到哪里?可以通过实例确认各种编译器下面最终异常的处理是terminate()函数。terminal()函数是整个程序释放资源的最后机会,函数可以自定义这个函数,但是不能够在这个函数中再次抛出异常。
2.1. terminate()函数重构
首先,自定义一个无返回值无参数的函数,这个函数不抛出任何异常,同时一某种方式直接结束当前的应用程序。其次,调用set_terminate()设置自定义的结束函数,参数类型是void(*)(),返回值默认为terminate()函数入口地址。
#include <iostream>
#include <cstdlib>
#include <exception>
using namespace std;
void my_terminate()
{
cout << "void my_terminate()" << endl;
exit(1);
}
class Test
{
public:
Test()
{
cout << "Test()";
cout << endl;
}
~Test()
{
cout << "~Test()";
cout << endl;
}
};
int main()
{
set_terminate(my_terminate);
static Test t;
throw 1;
return 0;
}
2.2 析构函数不能抛出异常
#include <iostream>
#include <cstdlib>
#include <exception>
using namespace std;
void my_terminate()
{
cout << "void my_terminate()" << endl;
exit(1);
}
class Test
{
public:
Test()
{
cout << "Test()";
cout << endl;
}
~Test()
{
cout << "~Test()";
cout << endl;
throw 2;
}
};
int main()
{
set_terminate(my_terminate);
static Test t;
throw 1;
return 0;
}
exit(1)被调用到的时候,再次出发对象的析构函数,在析构函数中又一次抛出异常,导致自定义的结束函数又一次被调用。如果在结束函数中释放资源,就会造成多次释放,容易造成崩溃。因此,我们将exit换成abort,这也是默认结束函数的行为,abort会直接结束程序。实际编程时不要在析构函数中扔出异常。