一、程序的错误
1.编码错误:编译阶段
2.设计错误:测试阶段
3.环境错误:使用阶段
4.应用错误:测试和使用阶段
二、错误处理机制
1.通过返回值处理错误
当一个函数在执行过程中发生了某种错误,通过函数的返回值告诉函数调用者。
malloc/fopen - 成功返回有效指针,失败返回NULL
返回无效值表示失败
返回0表示成功,返回-1表示失败
1)优点:简单、避免崩溃、局部对象有机会析构。
2)缺点:逐层检查返回值
A->B->C
2.通过远程跳转处理错误
1)优点:不需要逐层检查返回值,简化错误处理流程
2)缺点:局部对象没有机会析构,形成内存泄漏。
3.C++异常机制
注意:goto 不能跨函数!
三、异常语法
抛出异常:
throw 异常对象;
如:
throw -1;
throw 3.14;
throw "error";
class A { ... };
throw A ();
A a (...);
throw a;
捕获异常:
try {
可以引发异常的语句;
}
补充:
执行右括号,保证析构。
catch (异常类型1 异常对象) {
针对异常类型1的处理;
}
catch (异常类型2 异常对象) {
针对异常类型2的处理;
}
...
catch (...) {
针对其它异常类型的处理;
}
如:
try {
foo ();
bar ();
hum ();
}
catch (int ex) {
// ...
}
catch (double ex) {
}
catch (string& ex) {
}
catch (A& ex) {
}
catch (...) {
}
四、使用异常的注意事项
1.抛出基本类型的异常,通过异常对象的值区分不同错误。
2.抛出类类型的异常,通过异常对象的类型区分不同错误。
3.通过异常类型携带更多的诊断信息。
4.异常说明
1)在函数的定义和声明中可以加入异常说明子句,表示该函数可能抛出的异常类型。
2)如果函数抛出了其异常说明以外的异常,该异常无法在程序中捕获。
3)所有未被捕获的异常最终都会交由std::unexpected()函数处理,缺省情况下该函数会调用std::terminate()函数,而后者又会调用abort()函数终止进程。
4)一个函数如果没有异常说明,表示它可以抛出任何异常,如果有throw ()异常说明,表示它不抛出任何异常。
5)如果函数声明和定义分开,那么在其声明和定义部分必须使用完全相同的异常说明。
6)子类中对基类虚函数的覆盖版本不能说明比基类版本抛出更多的异常。
5.忽略异常、继续抛出原异常、继续抛出新异常
忽略异常:一个函数对于它所收到的异常可以不catch,该异常就会继续向其调用者抛出,直到被catch为止,如果所有的函数都不catch该异常,最终被系统捕获,终止进程。
继续抛出原异常:throw;
继续抛出新异常:在catch中throw新的异常对象
五、在构造函数中抛出异常
1.在构造函数中可以抛出异常,而且某些情况只能通过异常表示构造过程中出现的错误。
2.如果一个对象在构造过程中抛出了异常,那么这个对象将被不完整构造,而一个不完整构造的对象,其析构函数永远不会被执行。因此就需要在抛出异常之前手动地释放所有动态分配的资源,这些资源构造函数的回滚机制无法自动释放,或者使用智能指针。
六、在析构函数中最好不要抛出异常
try {
A a;
a.foo ();
}
catch (...) {
...
}
将可能引发的异常内部消化。
七、标准库异常
1.编码错误:编译阶段
2.设计错误:测试阶段
3.环境错误:使用阶段
4.应用错误:测试和使用阶段
二、错误处理机制
1.通过返回值处理错误
当一个函数在执行过程中发生了某种错误,通过函数的返回值告诉函数调用者。
malloc/fopen - 成功返回有效指针,失败返回NULL
返回无效值表示失败
返回0表示成功,返回-1表示失败
1)优点:简单、避免崩溃、局部对象有机会析构。
2)缺点:逐层检查返回值
A->B->C
2.通过远程跳转处理错误
1)优点:不需要逐层检查返回值,简化错误处理流程
2)缺点:局部对象没有机会析构,形成内存泄漏。
3.C++异常机制
注意:goto 不能跨函数!
三、异常语法
抛出异常:
throw 异常对象;
如:
throw -1;
throw 3.14;
throw "error";
class A { ... };
throw A ();
A a (...);
throw a;
捕获异常:
try {
可以引发异常的语句;
}
补充:
执行右括号,保证析构。
catch (异常类型1 异常对象) {
针对异常类型1的处理;
}
catch (异常类型2 异常对象) {
针对异常类型2的处理;
}
...
catch (...) {
针对其它异常类型的处理;
}
如:
try {
foo ();
bar ();
hum ();
}
catch (int ex) {
// ...
}
catch (double ex) {
}
catch (string& ex) {
}
catch (A& ex) {
}
catch (...) {
}
四、使用异常的注意事项
1.抛出基本类型的异常,通过异常对象的值区分不同错误。
2.抛出类类型的异常,通过异常对象的类型区分不同错误。
3.通过异常类型携带更多的诊断信息。
4.异常说明
1)在函数的定义和声明中可以加入异常说明子句,表示该函数可能抛出的异常类型。
2)如果函数抛出了其异常说明以外的异常,该异常无法在程序中捕获。
3)所有未被捕获的异常最终都会交由std::unexpected()函数处理,缺省情况下该函数会调用std::terminate()函数,而后者又会调用abort()函数终止进程。
4)一个函数如果没有异常说明,表示它可以抛出任何异常,如果有throw ()异常说明,表示它不抛出任何异常。
5)如果函数声明和定义分开,那么在其声明和定义部分必须使用完全相同的异常说明。
6)子类中对基类虚函数的覆盖版本不能说明比基类版本抛出更多的异常。
5.忽略异常、继续抛出原异常、继续抛出新异常
忽略异常:一个函数对于它所收到的异常可以不catch,该异常就会继续向其调用者抛出,直到被catch为止,如果所有的函数都不catch该异常,最终被系统捕获,终止进程。
继续抛出原异常:throw;
继续抛出新异常:在catch中throw新的异常对象
五、在构造函数中抛出异常
1.在构造函数中可以抛出异常,而且某些情况只能通过异常表示构造过程中出现的错误。
2.如果一个对象在构造过程中抛出了异常,那么这个对象将被不完整构造,而一个不完整构造的对象,其析构函数永远不会被执行。因此就需要在抛出异常之前手动地释放所有动态分配的资源,这些资源构造函数的回滚机制无法自动释放,或者使用智能指针。
六、在析构函数中最好不要抛出异常
try {
A a;
a.foo ();
}
catch (...) {
...
}
将可能引发的异常内部消化。
七、标准库异常
hexcump -c text.cpp
一个小小示例:
#include <iostream>
using namespace std;
//void foo (int i) throw (int, float) {
//void foo (int i) throw () {
void foo (int i) {
if (i == 1)
throw 10;
if (i == 2)
throw 3.14f;
if (i == 3)
throw "Hello, World !";
}
class A {
public:
virtual void foo (void) throw () {}
};
class B : public A {
public:
void foo (void) throw () {
throw -1;
}
};
int main (void) {
try {
foo (3);
}
catch (int ex) {
cout << ex << endl;
}
catch (float ex) {
cout << ex << endl;
}
catch (const char* ex) {
cout << ex << endl;
}
catch (...) {
cout << "其它异常!" << endl;
}
return 0;
}
//
Hello, World !