曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 4248|回复: 5
打印 上一主题 下一主题

异常处理

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2011-11-19 16:23:51 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
异常处理是程序用来处理运行时错误的一种结构化方法。C++ 的异常处理机制建立在 3 个关键字上:try, catch 和 throw 。一般情况是:

在 try 代码块中包含你想要监控的程序部分。如果 try 中的程序发生了一个异常,那么这个异常将被抛出( throw ),然后我们使用 catch 来捕获并处理这个异常。通用形式如下:
[C++] 纯文本查看 复制代码
try {
       // try 代码块
}
catch (type1 arg) {
   //catch 代码块
}
catch (type2 arg) {
   //catch 代码块
}
...
catch (typeN arg) {
   //catch 代码块
}

try 中必须包含想要监控的程序段,这个程序段可能是一个函数中的几条语句,也可以是整个 main() 函数(这样整个程序都将被监控)。

当 try 中的程序抛出一个异常时,它将被相应的 catch 语句捕获并处理。catch 语句可以有多个,每个语句中指定某个与异常匹配的类型,一旦发生匹配,那么这个 catch 语句将被执行,而所有其他的 catch 语句都会被忽略。当异常被捕获时,变量 arg 用来接收异常的值。程序可以捕获任意类型的数据,也包括自己创建的类型。

throw 语句的通用形式如下:
throw exception
throw 语句生成由 exception 指定的异常,如果要抛出这个异常,则 throw 就要放在 try 中,或者 try 中代码所调用的函数中(直接或间接)。

下面是一个简单的示例程序,演示了异常处理过程:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

int main()
{
        cout << "start\n";

        try {   //try 程序块
                cout << "Inside try block\n";
                throw 99;       //抛出异常
                cout << "This will not execute";
        }
        catch (int i) { //捕获异常
                cout << "Caught an exception -- value is: ";
                cout << i << "\n";
        }

        cout << "end\n";

        return 0;
}

运行输出:
start
Inside try block
Caught an exception -- value is: 99
end
由程序的输出可见,当 try 中抛出异常后,程序的执行流程转移到 catch 语句,try 同时结束。也就是说,catch 语句不是被调用,而是程序的执行流程转移到了 catch 语句中。这样,throw 语句后面的 cout 语句就永远得不到执行。在执行完 catch 语句后,程序的流程是继续执行 catch 后面的语句。

注意:如果抛出的异常没有与之相配的 catch 语句,那么将发生非常长的程序终止。如果程序抛出了一个未被处理的异常,系统将调用 C++ 标准库中 的函数 terminate() 。默认情况下,terminate() 会调用函数 abort() 来终止程序,如果愿意,你也可以自定义一个程序终止处理函数。

比如在上面的程序中,如果 catch 语句中的异常类型改为 double,那么抛出的异常将不会被捕获,从而产生非正常的程序终止,如下所示:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

int main()
{
        cout << "start\n";

        try {   //try 程序块
                cout << "Inside try block\n";
                throw 99;       //抛出异常
                cout << "This will not execute";
        }
        catch (double i) {      //捕获异常
                cout << "Caught an exception -- value is: ";
                cout << i << "\n";
        }

        cout << "end\n";

        return 0;
}

运行输出(G++ 编译器,编译器不同输出的信息也可能不同):
start
Inside try block
terminate called after throwing an instance of 'int'
Aborted

在 try 中调用的函数所抛出的异常也可以被该 try 块捕获,比如:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

void Xtest(int test)
{
   cout << "Inside Xtest, test is: " << test << "\n";
   if(test) throw test;
}
int main()
{
   cout << "start\n";
  
   try {   //try 块
         cout << "Inside try block\n";
         Xtest(0);      //try 块中的 Xtest() 函数抛出的异常也能被捕获
         Xtest(1);
          Xtest(2);
}
catch (int i) {   //捕获异常
   cout << "Caught an exception -- value is: ";
   cout << i << "\n";
}
cout << "end";

return 0;
}

运行输出:
[C++] 纯文本查看 复制代码
start
Inside try block
Inside Xtest, test is: 0
Inside Xtest, test is: 1
Caught an exception -- value is: 1


try 也可以用在函数中。在这种情况下,每次程序执行该函数时,与函数相关的异常处理将被复位,如下所示:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

void Xhandler(int test)
{
        try {
                if (test) throw test;
        }
        catch (int i) {
                cout << "Caught One. Ex. #: " << i << '\n';
        }

        cout << "function end.\n";
}

int main()
{
        cout << "start\n";

        Xhandler(1);
        Xhandler(2);
        Xhandler(0);
        Xhandler(3);

        cout << "end\n";

        return 0;
}

运行输出:
start
Caught One. Ex. #: 1
function end.
Caught One. Ex. #: 2
function end.
function end.
Caught One. Ex. #: 3
function end.
end
从输出结果中可见,程序中共抛出 3 个异常,在每次抛出异常后,函数将返回。当函数再次被调用时,异常处理也将被复位

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
沙发
 楼主| 发表于 2011-11-19 22:51:58 | 只看该作者

使用多个 catch 语句

一个 try 代码块可以有多个 catch 语句。每个 catch 语句所能捕获的异常必须是不同类型,如下程序所示:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

void Xhandler(int test)
{
        try {
                if (test) throw test;
                else throw "Value is zero";
        }
        catch (int i) {
                cout << "Caught one! Ex. #: " << i << '\\n';
        }
        catch (const char *str) {
                cout << "Caught a string: ";
                cout << str << '\\n';
        }
}

int main()
{
        cout << "start\\n";

        Xhandler(1);
        Xhandler(2);
        Xhandler(0);
        Xhandler(1);

        cout << "end";

        return 0;
}

运行输出:
start
Caught one! Ex. #: 1
Caught one! Ex. #: 2
Caught a string: Value is zero
Caught one! Ex. #: 1
end
从输出可以看到,每个 catch 语句仅相应与自己定义的类型匹配的异常。一般地,程序按照 catch 语句在程序中的顺序来检查每个 catch 语句。只能有一个匹配的 catch 语句被执行,而其他语句将被忽略。

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
板凳
 楼主| 发表于 2011-11-20 00:54:08 | 只看该作者

捕获基类的异常

在 catch 语句中的异常类型可以是类,这里就存在一个问题:在多个 catch 语句中定义的异常类型之间存在派生关系。那么这里有一点很重要:如果某个 catch 语句能够捕获基类型的异常,那么它也能捕获派生类型的异常。也就是说,如果是一个 catch 原本用来捕获基类异常的,但是如果一个派生类也抛出异常,那么该 catch 语句也可以将之捕获。如下所示:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

class B {
};

class D : public B {
};

int main(int argc, char **argv)
{
        B base;
        D derived;

        try {
                throw base;
        }

        catch (B b) {
                cout << "Caught a base calss.\\n";
        }

        return 0;
}

运行输出:
Caught a base calss.
如果将上面的 throw base; 换成 throw derived ,catch 照样可以捕获。

所以,如果你既希望可以捕获基类型又可以捕获派生类型的异常,那么应该将捕获派生类型异常的 catch 语句放在 catch 系列语句的前面。若不这么做,那么捕获基类型异常的 catch 语句将捕获所有基类型和派生类型的异常了,而捕获派生类型异常的 catch 语句就会被忽略。如下面程序所示:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

class B {
};

class D : public B {
};

int main()
{
        D derived;

        try {
                throw derived;
        }
        catch (B b) {
                cout << "Caught a base class.\\n";
        }
        catch (D d) {
                cout << "This won't execute.\\n";
        }

        return 0;
}

上面程序,由于对象 derived 的类是 D,而 D 的基类又是 B,所以它将被第一个 catch 语句所捕获,而第二个 catch 语句将永远不会执行到。对于这种情况,有些编译器会报错而无法生成可执行文件,有些编译器则会发出警告,但仍然可以产生可执行文件,比如 g++ :
g++ catchbase.cc -o catchbase
catchbase.cc: In function ‘int main()’:
catchbase.cc:20:2: warning: exception of type ‘D’ will be caught [enabled by default]
catchbase.cc:17:2: warning:    by earlier handler for ‘B’ [enabled by default]

修改这种错误的方法是将 catch 语句的顺序调过来。

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
地板
 楼主| 发表于 2011-11-20 10:50:34 | 只看该作者

捕获所有的异常

在某些情况下,我们可能希望在一个异常处理中捕获所有的异常,而不只是捕获某种类型的异常,C++ 对此提供支持,使用下面这种形式的 catch 语句即可:
catch(...) {
//处理所有的异常
上面 catch 中的省略号表示 catch 语句可以捕获任意类型的异常。示例程序:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

void Xhandler(int test)
{
        try {
                if(test == 0) throw test;       //抛出整数异常
                if(test == 1) throw 'a';        //抛出字符异常
                if(test == 2) throw 123.23;     //抛出double异常
        }
        catch(...) {    //捕获所有异常
                cout << "Caught One!\\n";
        }
}

int main()
{
        cout << "start\\n";

        Xhandler(0);
        Xhandler(2);
        Xhandler(2);

        cout << "end";

        return 0;
}

运行输出:
start
Caught One!
Caught One!
Caught One!
end
从上可以看到,一条 catch 语句就能捕获 3 个异常。

catch(...) 语句最好的地方是把它放在一组 catch 语句的最后一个。在这种情况下,catch(...) 语句就是一个很有用的默认语句。比如下面程序除了显式地捕获整数异常外,还增加了 catch(...) 来捕获其他的异常:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

void Xhandler(int test)
{
        try {
                if(test == 0) throw test;       //抛出整数异常
                if(test == 1) throw 'a';        //抛出字符异常
                if(test == 2) throw 123.23;     //抛出double异常
        }
        catch(int i) {  //捕获整数异常
                cout << "Caught " << i << endl;
        }
        catch(...) {    //捕获所有异常
                cout << "Caught One!\\n";
        }
}

int main()
{
        cout << "start\\n";

        Xhandler(0);
        Xhandler(2);
        Xhandler(2);

        cout << "end";

        return 0;
}

运行输出:
start
Caught 0
Caught One!
Caught One!
end
从上看到,使用 catch(...) 作为默认的 catch 语句是个很好的办法,它能够捕获所有你不想显式处理的异常。同时,通过捕获所有的异常,可以防止在程序中抛出一个未处理的异常而导致程序非正常终止。

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
5#
 楼主| 发表于 2011-11-20 14:37:09 | 只看该作者

对函数抛出异常的限制

可以现在在一个函数中所能抛出的异常,事实上也可以让一个函数不抛出任何类型异常,实现这种做法,需要如下形式声明函数:
ret-type func-name(arg-list) throw(type-list)
{
   //...
}
上面,ret-type 是函数返回类型,arg-list 是函数的参数,throw 里面的 type-list 就是只希望抛出的异常,如果你不想在函数中抛出任何类型的异常,那么将 typelist 留空

注意:如果函数中试图抛出一个不支持的异常,那么标准库函数 unexpected() 就会被调用,在默认情况下,该函数将调用 abort() ,从而使程序非正常终止。当然,如果你不愿意,那么也可以指定自己的程序终止处理函数。

下面程序演示了如何限制一个函数所能抛出的异常:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

void Xhandler(int test) throw(int, char, double)
{
 
        if(test == 0) throw test;
        if(test == 1) throw 'a';
        if(test == 2) throw 123.23;

}

int main()
{
        cout << "start\\n";

        try {
                Xhandler(0);
        }

        catch(int i) {
                cout << "Caught int\\n";
        }

        catch(char c) {
                cout << "Caught char\\n";
        }

        catch(double d) {
                cout << "Caught double\\n";
        }
       
        catch(const char *p) {
                cout << "Caught const char\\n";
        }

     cout << "end" << endl;

        return 0;
}

运行输出:
start
Caught int
end
如果我们将函数 Xhandler() 里的  if(test == 0) throw test; 改为  if(test == 0) throw "hello"; 那么再次编译运行程序时会看到:
start
terminate called after throwing an instance of 'char const*'
Aborted
这时候试图抛出不在列表所允许的异常,将产生非正常的程序终止。

还需要清楚的是:只有当函数中抛出的异常再次抛给调用该函数的 try 语句时,这个限制才起作用。也就是说,这个限制只有当异常被抛出到函数外的代码时才起作用,如果在函数内部,则没有此限制。
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

void Xhandler(int test) throw(int, char, double)
{
        //在函数内部抛出不在列表中限制的异常是可以的
        try {
                if(test) throw "hello world";
        }
        catch (const char *p) {
                cout << "Caught " << p << endl;
        }

        if(test == 0) throw test;
        if(test == 1) throw 'a';
        if(test == 2) throw 123.23;


}

int main()
{
        cout << "start\\n";

        try {
                Xhandler(1);
        }

        catch(int i) {
                cout << "Caught int\\n";
        }

        catch(char c) {
                cout << "Caught char\\n";
        }

        catch(double d) {
                cout << "Caught double\\n";
        }

        catch(const char *p) {
                cout << "Caught const char\\n";
        }

        cout << "end" << endl;

        return 0;
}

运行输出:
start
Caught hello world
Caught char
end

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
6#
 楼主| 发表于 2011-11-20 19:30:51 | 只看该作者

再次抛出异常

我们可以在异常处理模块中再次抛出一个异常,此时在该模块中只要使用单独的 throw 语句,而无需在其后指定异常。这将使得当前的异常被传递给更外层的 try/catch 语句序列。如下程序所示:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

void Xhandler()
{
        try {
                throw "hello";  //抛出 const char * 类型异常
        }

        catch(const char *) {           //捕获 const char * 类型异常
                cout << "Caught char * inside Xhandler\\n";
                throw;          //在函数中再次抛出 const char * 类型异常,被外层的 catch 捕捉
        }
}

int main()
{
        cout << "start\\n";
        try {
                Xhandler();
        }
        catch(const char *) {
                cout << "Caught const char * inside main\\n";
        }

        cout << "end" << endl;

        return 0;
}

运行输出:
start
Caught char * inside Xhandler
Caught const char * inside main
end
由上面程序看到,在 Xhandler() 函数的 catch 语句中使用了 throw 再次抛出了 const char * 类型的异常,当然后面没有必要再次指定这个类型。这个再次抛出的异常不会被同一个 catch 语句捕获,而是被传递到最近的一个外层 try/catch 语句序列中---这里是在 main 里。

通过这种方式调用 throw 的最好理由是:它允许多个异常处理模块处理同一个异常。例如,一个异常处理模块可能只是处理异常的某个方面,而第二个异常处理模块则处理异常的其他方面。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-17 21:50 , Processed in 0.071976 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表