C学习大纲异常处理机制总结(C学习大纲异常处理机制总结)(1)

C 异常处理机制总结

参考文档:《C 编程思想》《C Primer》《More effective C 》

一、 传统的错误处理机制:

1. 返回值或全局错误状态标志。

缺点:需要冗长的错误检查代码。

2. C standard Library中的信号处理系统,signal函数。

缺点:信号处理机制比较复杂;耦合度高;复杂系统中的信号容易产生冲突。

3. C standard Library中的非局部跳转函数,setjmp和longjmp。

缺点: C 的类析构函数不会被调用,对象不能正确被清理,造成内存泄漏;耦合度高。

二、 C 异常处理机制介绍:

1. 语法:

try

{

throw 3.0;

}

catch (int) //如果处理块内不需要该变量,可以省略形参

{

cout << "int exception happened!" << endl;

}

catch (double d)

{

cout << d << " double exception happened!" << endl;

}

catch (…)

{

cout << "unknown exception happened!" << endl;

}


2. 说明:

1) throw 可以抛出一个任意类型的变量(或表达式)(包括内置类型如int的变量,或者自定义的类型的变量),catch 按照被抛出变量的编译时类型进行匹配,找到第一个匹配的类型即进入异常处理。

2) 异常处理是为了保证使程序能够不异常退出。

3) 建议:尽量不要使用throw抛出内置类型的变量。

4) 如果throw抛出的异常找不到匹配的类型,最终程序将调用C standard Library的terminate函数,程序将异常退出。

5) 当程序跳出try块时,try块内的局部变量被自动释放,对象的析构函数被调用。所以,为了保证程序不异常退出,应该保证析构函数不会抛出异常。

6) 调用函数时,程序的控制权最终还会返回到函数的调用处,但是当你抛出一个异常时,控制权永远不会回到抛出异常的地方。

7) 异常匹配时,只允许三种类型转换:const与非const;派生类与基类;数组与指针。(注意:不允许算术转换.)

8) 建议:catch子句的次序必须反映类型层次,派生类放到基类前面。

9) throw出的对象称为异常对象(exception object),由编译器管理,catch接受到的对象如果不是引用或指针的话,则进行对象拷贝。但是异常对象是程序结束才被释放。

10) 异常可以发生在构造函数中或者构造函数初始化式中。注意:如果异常发生在构造函数中,对象的析构函数将不会被调用!所以需要在构造函数中进行try-catch自己释放资源。另外,为了处理构造函数初始化式中可能发生的异常,语法应该修改为如下:

//normal constructor

UserClass(): m_nA(1), m_nB(2) { /*constructor body*/ }

//constructor using function try block

UserClass() try: m_nA(1), m_nB(2){ /*constructor body*/ }catch(…){ }

11) 标准库异常类定义在<exception><stdexcep>头文件中。


3. 重新抛出:

在catch块或被catch块调用的函数中,可以用”throw;”语句(throw空对象)将异常重新抛出。

4. 异常规格说明(exception specification):

说明函数将会抛出什么类型的异常。

例子:

void func(int i) throw (runtime_error); //说明该函数func有可能抛出runtime_error异常

void func(int i) throw(); //说明函数func不会抛出任何异常

1) 如果函数运行时抛出了其他类型的异常,程序将会调用标准库的unexpected函数,该函数将调用terminate退出程序。

2) 派生类的虚函数的异常规格说明只能和基类一样或比基类更严格,不能增加新的异常类型。

3) 注意:一般只会使用throw()来说明一个函数是安全的,不会抛出任何异常,这样编译器就可以对调用该函数的代码做出优化。一般不会使用其他的异常规格说明。

5. 标准异常库:(见<exception><stdexcep><new><typeinfo>头文件)

其中,类exception的定义如下:

class exception

{

public:

exception();

exception(const char* const &);

exception(const exception&);

exception& operator= (const exception&);

virtual ~exception();

virtual char* what() const; //获得信息

private:

const char* _m_what;

int _m_doFree;

};


三、 使用异常处理机制的建议:

来自《C 编程思想》和《More Effective C 》的建议:

1. 不要使用异常的情形:

1) 绝对不要在异步事件中使用异常。如使用了信号机制的系统、中断处理程序等。

2) 不要在处理简单错误的时候使用异常。一般情况下,自己可以处理的错误就直接处理,只有当在此处处理不了的错误才抛出到更大的语境中。

3) 不要将异常用于流程控制,比如代替switch语句。因为异常处理效率非常低,而且编译器会做出很多程序员不知道的事情。

4) 遇到不可恢复的错误时,最好不用处理异常,直接将异常交给操作系统处理即可。

2. 应该使用异常的情形:

1) 遇到可以修正的错误,通过一些行为可以使程序继续执行;

2) 在当前的语境中不能完全处理的错误,但有可能在较高层的语境中可以处理,这时,可以通过将一个同样类型或者不同类型的异常抛出;

3) 发生不足以让程序退出的错误,但是你认为该错误是致命错误的时候,可以通过抛出异常便于及时发现故障而终止程序;

4) 为了简化错误处理。

3. 对使用异常的建议(1,4,5,6见MoreEffectiveC ):

1) 慎重使用异常规格说明:当不确定函数抛出何种异常时,最好不要使用异常规格说明。

2) 尽量使用标准异常库的异常类:编写自定义的异常类之前,先查看标准异常库。如果标准异常库没有需要的语义的异常,尽量从标准异常类派生出自定义的异常类。

3) 建立自己的异常类层次结构。

4) 尽量通过引用来捕捉异常,原因有二:防止对象拷贝;防止对象slicing。异常对象由编译器统一管理,可以放心使用引用来操作。

5) 可以在构造函数中抛出异常:如果构造函数发生故障而没有及时发现,程序继续运行会造成不可预料的灾难性结果,这种情况下可以在构造函数中抛出异常。

6) 不要在析构函数中抛出异常。所以有时候需要在析构函数中处理异常。


四、 编码规范:

1. 函数的参数检查不合法时,抛出标准库的invalid_argument(logic_error子类)异常。

2. 调用函数时,应该检查logic_error异常,并做相应处理,该异常不足以使程序终止。

3. 发生致命逻辑错误或者不可恢复错误时,不要捕捉抛出的异常,直接将其交给操作系统处理,退出程序。

4. 尽量使用标准异常库的异常类,没有合适的标准异常时,建立自己的异常层次结构。

5. 自定义异常类时,应该从标准异常库中的类派生,异常类的名称应该能反映出错误的类型。

6. 使用引用来捕捉异常。

7. 不要使用catch(…)。

8. 捕捉到未知异常时,要重新抛出,以免发生故障后继续运行程序,造成不可预料的后果。

9. 尽量将资源释放写入析构函数,这样防止发生异常时,资源不能释放。

10. 不要使用异常规格说明。??讨论??

11. 建立一个系统的异常基类,将所有系统中发生的异常都封装成该基类的子类。 ??讨论??

五、 例子:

#include <iostream>

#include <exception>

using namespace std;

void func(int i)

{

if (i>100)

{

throw invalid_argument("invalid argument: argument can not be bigger than 100");

}

//do something…

}

int main()

{

int i;

cout << "Please input a number less than 100:" << endl;

cin >> i;

try

{

func(i);

}

catch (logic_error& e) //caught by reference

{

cout << "invalid input: " << e.what() << endl; //logic_error, can be handled, do not need to abort

}

catch (exception&)

{

//do something…

throw; //unknown exception caught, throw again

}

return 0;

}


六、 对使用new操作符分配内存失败的说明:

较新的C 标准中规定,普通的new操作符失败时,抛出std::bad_alloc异常。但是在以前,new失败是简单的返回一个NULL指针。在一定的环境下,返回一个NULL指针来表示一个失败依然是一个不错的选择。C 标准委员会意识到这个问题,所以他们决定定义一个特别的new操作符版本,这个版本返回0表示失败。这就是nothrow new。

nothrow new只是简单的给operator new加了一个参数。

相关代码如下:

class nothrow_t // in namespace std

{}; //empty class

extern const nothrow_t nothrow; //an object , in namespace std

//declarations from <new>

void * operator new (size_t size, const std::nothrow_t &);

//array version

void * operator new[] (size_t size, const std::nothrow_t &);

所以,使用nothrow new时,可以如下使用:

#include <new>

#include <iostream> // for std::cerr

#include <cstdlib> // for std::exit()

Task * ptask = new (std::nothrow) Task;

if (!ptask)

{

std::cerr<<"allocation failure!";

std::exit(1);

}

也可以创建你自己的nothrow_t对象来完成相同的效应:

#include <new>

std::nothrow_t nt;

Task * ptask = new (nt) Task; //user-defined argument

if (!ptask)

//...

值得一提的是,在VC6和VC2005编译器下,默认的new操作符失败的行为也是简单的返回一个NULL指针,它们并不是遵循C 标准抛出一个std::bad_alloc异常。

————————————————


C学习大纲异常处理机制总结(C学习大纲异常处理机制总结)(2)

通过分享实用的计算机编程语言干货,推动中国编程到2025年基本实现普及化,使编程变得全民皆知,最终实现中国编程之崛起,这里是中国编程2025,感谢大家的支持。

原文链接:https://blog.csdn.net/MulinB/article/details/1763240

,