1) C异常处理及其优缺点
l 返回值
每次调用都需要检查,导致代码膨胀,难以阅读主要逻辑。
l 全局错误状态 – _set_errno() & _get_errno()
1) 当无法用返回值传输错误状态时,则用全局变量errno。在errno.h可查询所有已定义的错误码。
这是线程安全的。
2) 在多线程下,每个线程有自己的errno(线程局部存储TLS)。
3) 什么情况无法用返回值返回错误码
比如[]重载:
A& operator [] const (int i)
{
}
不能返回NULL,因为是引用,也不能返回别的值代表错误。
l 信号处理 – signal & raise
1) 信号处理相对较为复杂,信号只有7个,不能重定义,传异常信息也是问题。
2) 使用信号,需包含<signal.h>
3) 在windows中只有这几个信号
#define SIGINT 2 /* interrupt */
#define SIGILL 4 /* illegal instruction - invalid function image */
#define SIGFPE 8 /* floating point exception */
#define SIGSEGV 11 /* segment violation */
#define SIGTERM 15 /* Software termination signal from kill */
#define SIGBREAK 21 /* Ctrl-Break sequence */
#define SIGABRT 22 /* abnormal termination triggered by abort call */
4) 用法如下:
void Func(int signal)
{
//处理
}
int main()
{
signal(SIGINT, Func);
//逻辑处理
raise(SIGINT);//这时候Func运行了,然而,如果还有raise,必须重新设置signal
}
l 非局部跳转 – setjmp & longjmp
1) 其不会调用析构函数。
详细情况请参考
http://blog.csdn.net/yeming81/archive/2010/05/31/5637734.aspx
2) C++异常处理及其优缺点
l 异常优点
1) 将正常逻辑与错误处理分开
在一个try块里,对于同样的函数调用或不同函数抛出同样异常,只需要捕获一个异常即可
2) 异常不能被忽略
如果你不处理异常,那么程序就会终止(取决于terminate的行为)。
3) 异常发生后,会调用析构函数 (构造函数抛出异常时,析构函数不会调用)
l 异常缺点
1) 性能损失
异常是会影响一点性能的,编译器会插入一些指令来处理异常出现时所应该有的动作。但是,和异常带来的优点相比,这点性能损失还是值得的。
2) 一个事实
STL出于效率考虑,只抛出运行时刻异常,不检查逻辑错误。
l 异常模型
1) 终止
遇到异常处理后,不会接着再尝试执行失败方法。目前,大部分都是这种模型。
2) 恢复
和终止不同,它会再次尝试执行失败的方法。
try
{
while(true)
{
}
}
catch(…)
{
}
l 异常捕获时的匹配
1) 数据类型不能转换
char、int、long、double、float之间不能转换
比如,抛出一个int类型的异常,想捕获long类型的异常是捕获不到的。
2) 不支持自动转换
关于自动转换,请参考
http://blog.csdn.net/yeming81/archive/2010/05/31/5637722.aspx
3) 子类异常可以被父类捕获匹配
所以,一般将父类捕获放到最后。
4) …捕获所有异常
catch(…)一般用于释放资源,然后选择重新抛出异常,因为无法得到异常参数。
l 重新抛出异常
try
{
}
catch(…或者特定异常)
{
throw; //无须带异常对象,其会把当前异常对象传到上一层。
}
l 构造函数初始化列表异常的捕获
初始化列表比较特殊,不在函数体里,那么异常如何捕获呢?
可用函数级的try:
class B: public A
{
public:
B() try: A()
{
//初始化
}
catch(…)
{
}
};
函数级的try不一定要用在初始化列表,但是用在别处也没啥用啊。
l 异常不能被捕获的情况
1) 全局、静态(全局或局部)对象的构造和析构函数抛出异常时,是无法被捕获的(因为不知道在哪里捕获),默认将调用terminate函数。
2) 当抛出一个异常时,是先要析构对象,才能处理异常的。这时候,如果析构函数再抛出一个异常,异常处理过程被中断,默认将调用terminate函数。
try
{
A a;
a.Func(); //假设Func会抛出异常,A的析构函数也会抛出异常
}
catch(…)
{
//处理
}
l 重定义异常退出
1) 异常没有在任何一层被捕获处理时,默认将调用terminate函数(<exception>定义),从而abort函数被调用,程序非正常退出(析构函数未被调用)
关于abort,请参考
http://blog.csdn.net/yeming81/archive/2010/06/16/5673052.aspx
2) 重定义terminate
void myFunc(){}
void (*restore_terminate)() = set_terminate(myFunc); //保存默认值,以便用完可以恢复
l 标准异常对象
1) 能够用标准异常就不要自己定义异常,其通用性、可读性较强。特别是编写程序库时,如果抛出自定义异常,那么将来换别的库时,客户必须要修改代码以支持新的异常。
2) 所有标准异常的根在于exception基类<exception>。
主要包含what方法返回错误描述。基类不包含以string为参数的构造函数,所以,不能throw exception(“Error”); 一般不从这个根类直接继承,从下面的派生类继承。
exception下的直接派生类有如下:
3) 逻辑错误异常logic_error <stdexcept>
一般可以通过检测代码找出异常。以下派生自logic_error:
l domain_error
值不属于这个领域。比如数学计算方面acos(2.0)。
l invalid_argument
无效参数,参数不一致。
l length_error
超出了值域。比如,给某个字符串附加太多的字符。
l out_of_range
数组越界。
l ios_base::exception<ios>
IO操作异常。
4) 运行时异常runtime_error <stdexcept>
运行时刻才暴露出来的异常。以下派生自runtime_error:
l range_error
数学计算方法。
l overflow_error
算术上溢。
l underflow_error
算术下溢。
以下可归为语言特性类异常:
5) bad_cast <typeinfo>
dymatic_cast无法转换类型
6) bad_typeid <typeinfo>
typeid(NULL)
7) bad_alloc <new>
内存分配出错
8) bad_exception <exception>
抛出的异常再次不在规格说明里时,详细请看下文。
l 自定义异常
1) 一般不从exception直接继承
2) 需要实现what和构造函数
一个例子
class MyException: public logic_error
{
public:
MyException(string& content): logic_error(content)
{
}
const char* what() const throw()
{
return content;
}
};
l 异常规格说明(Exception Specification)
目前C++编译器还没有实现这个规格说明,如果用了,编译器会给出警告。
1) void Func() throw( Exception1, Exception2)
函数只能抛出Exception1和Exception2。
2) void Func() throw()
函数不能抛出任何异常
3) void Func()
函数可抛出任何异常
4) 异常规格说明是可以继承的
Ø 因为它作为函数声明的一部分,符合Is-A的关系,所以可以被继承。
Ø 派生类中同一函数的异常不能比基类的多,可以少。
因为客户使用基类的接口编程,如果你抛出的异常多的话,那么会出现非期望异常的抛出,请看下文。
Ø 如果基类抛出一个异常A,那么派生类可以抛出A的子类,但不可以抛出A的父类。
5) 非期望异常抛出
Ø 后果
如果抛出的异常未列在异常规格说明中,那么unexpected()函数(<exception>定义)将被调用,它默认调用terminate()函数,也就是abort()将被调用。
关于abort,请参考
http://blog.csdn.net/yeming81/archive/2010/06/16/5673052.aspx
Ø 如果bad_exception在异常规格说明中,并且所抛出的异常不在规格说明中,那么默认unexpected函数会将异常转换为bad_exception,这样就可以处理了。
Ø 更改unexpected默认行为
void myFunc(){}
void (*restore_unexpected)() = set_unexpected(myFunc); //保存默认值,以便用完可以恢复
Ø 在自定义的unexpected函数中,可选择重抛出异常或抛出另一个异常
void myFunc()
{
throw; //重抛异常
throw anotherException(); //抛出另一个异常
}
1) 如果抛出的异常还是不符合规格说明时,当异常规格说明包含bad_exception,异常对象被转换为bad_exception,然后匹配成功,异常得以处理。
2) 如果不包含bad_exception,程序会调用terminate()函数。
l 使用异常时的注意事项
1) 捕获异常时参数用引用
防止对象拷贝构造、子对象被切成父对象。
2) 析构函数不要抛出异常
3) 全局或静态对象的构造函数和析构函数都不要抛出异常
4) 如果构造函数要抛出异常,需要在构造函数而不是析构函数里释放资源,因为析构函数不会被调用
5) 当不知道可能抛出的异常时(使用模板时),不要使用规格说明。比如STL,因为类型是不确定的,无法知道里面的构造、拷贝构造函数是否会抛出异常(传参的时候)。
6) 当确定要规格说明时,一般把bad_exception加在里面,这样当抛出别的异常时也可以处理。否则,就会异常退出程序。