在传统的C语言,我们通过检查函数的返回值以判断调用是否成功,并处理各类异常情况,在Unix环境下可以通过设置error变量发出错误消息,并通过setjmp, longjmp来跳出深层次调用。C++语言在此基础上引入了Exception机制,遇到异常的程序模块可以throw一个异常,其他方法可以通过try catch来捕捉该异常。但是相比后来的Java与C#中的异常处理机制,C++的还是有不少待完善的地方:
1,缺少Exception基类,任何类都可以作为作为异常被抛出,且可以抛出对象,引用或者指针。
2,没有像Java那样约束方法,强制要求其处理或者抛出其内部所有可能发生的异常。比如:调用该方法的其他程序模块根本无法从接口中判断其是否会抛出异常,也就不会注意处理其异常。
正因为这样,造成C++程序员很少会使用其异常机制,我通常还是按照C语言的方式,利用返回值来区分各类错误,有时候偶尔在类的构造函数中会使用异常,因为在构造函数中产生的错误可能会造成生成的对象不正常,而构造函数没有返回值,因此只能抛出异常,或者设置一个状态标志位。
此外C++中异常机制的一大弊端就是会造成资源泄漏,比如:
void f()
{
Resource * r = new Resource();
FILE * f = fopen(“datafile.txt”);
DoSomething(); //这里可能会抛出异常,造成后面的代码无法执行,资源得不到及时释放。
delete r;
fclose(f);
}
为了解决资源泄漏的问题,当然不仅仅是因为异常引起的,还有可能是在程序编写过程中,某些地方不小心直接return了,或者为了避免在每一个返回语句前都加上一段delete, fclose之类的语句。在此我们需要用的C++的RAII机制:
RAII: Resource Acqusition is Initialization资源申请即初始化
其利用的原理就是堆栈上的对象在离开作用域时均会被自动调用析构函数,因此我们可以在构造函数中直接申请资源,在析构函数中释放资源,从而保证资源始终能够得到正常释放。
比如:
class File
{
public:
File(const char * file_name) { f = fopen(file_name); ….}
~File() {fclose(f);}
int write(const char *data, int len);
int read(char *data, int len);
private:
FILE *f;
}
另外std::auto_ptr也是基于这个原理构造的。
还有一点要提出的是,有些人喜欢在构造函数中将资源初始化为空,单独使用其他的方法来申请资源,比如在上述File的构造函数中令f=NULL,再添加一个open()方法来打开文件,我个人认为这是没有必要的,因为如果这样的话,那么在write与read方法中肯定每次都需要判断f是否不为NULL。具体的应用见仁见智,因此在C#与Java中貌似很多都有默认什么都不干的构造函数。