C/C++异常处理的对比

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)      数据类型不能转换

charintlongdoublefloat之间不能转换

比如,抛出一个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)

函数只能抛出Exception1Exception2

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加在里面,这样当抛出别的异常时也可以处理。否则,就会异常退出程序。

你可能感兴趣的:(exception,String,编译器,Signal,2010,数学计算)