C++ 异常处理

本文详细介绍了C++中的异常处理机制,包括异常处理的基本思想、优点、注意事项和标准库中的异常类。文章指出,异常处理使得错误处理与问题解决相分离,提高了程序的健壮性。同时,讨论了C++中使用异常时的性能问题、内存回收、函数异常规格说明等。文章还提供了异常的基本语法和处理流程,并强调了在构造函数中处理异常的重要性。
摘要由CSDN通过智能技术生成

From : http://www.uml.org.cn/c%2B%2B/201305272.asp

From : http://bbs.csdn.net/topics/390269815

From : http://developer.51cto.com/art/201512/502598.htm

From : http://www.cnblogs.com/ggjucheng/archive/2011/12/18/2292089.html

From : http://blog.csdn.net/dengzhouit/article/details/7012528

标准异常参考 http://www.cplusplus.com/reference/std/stdexcept/

什么是异常处理

一句话:异常处理就是处理程序中的错误。

为什么需要异常处理,以及异常处理的基本思想

C++之父Bjarne Stroustrup在《The C++ Programming Language》中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);另一方面,库的用户知道怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以再用户的代码里处理了,不用留给库去发现)。

Bjarne Stroustrup说:提供异常的基本目的就是为了处理上面的问题。基本思想是:让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。

The fundamental idea is that a function that finds a problem it cannot cope with throws an exception, hoping that its (direct or indirect) caller can handle the problem.

也就是《C++ primer》中说的:将问题检测和问题处理相分离。

Exceptions let us separate problem detection from problem resolution

一种思想:在所有支持异常处理的编程语言中(例如java),要认识到的一个思想:在异常处理过程中,由问题检测代码可以抛出一个对象给问题处理代码,通过这个对象的类型和内容,实际上完成了两个部分的通信,通信的内容是“出现了什么错误”。当然,各种语言对异常的具体实现有着或多或少的区别,但是这个通信的思想是不变的。

异常出现之前处理错误的方式

        在C语言的世界中,对错误的处理总是围绕着两种方法:一是使用整型的返回值标识错误;二是使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。

        当然C++中仍然是可以用这两种方法的。这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0表示出错;而有些函数返回0表示成功,返回非0表示出错。还有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。当然,你也可以过指针或者C++的引用来返回另外的值,但是这样可能会令你的程序略微晦涩难懂。

异常为什么好

在如果使用异常处理的优点有以下几点:

1. 函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其面的终止或出现错误的结果。

2. 整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。

3. 整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。

4. 异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。

C++中使用异常时应注意的问题

任何事情都是两面性的,异常有好处就有坏处。如果你是C++程序员,并且希望在你的代码中使用异常,那么下面的问题是你要注意的。

1. 性能问题。这个一般不会成为瓶颈,但是如果你编写的是高性能或者实时性要求比较强的软件,就需要考虑了。
(如果你像我一样,曾经是java程序员,那么下面的事情可能会让你一时迷糊,但是没办法,谁叫你现在学的是C++呢。)

2. 指针和动态分配导致的内存回收问题:在C++中,不会自动回收动态分配的内存,如果遇到异常就需要考虑是否正确的回收了内存。在java中,就基本不需要考虑这个,有垃圾回收机制真好!

3. 函数的异常抛出列表:java中是如果一个函数没有在异常抛出列表中显式指定要抛出的异常,就不允许抛出;可是在C++中是如果你没有在函数的异常抛出列表指定要抛出的异常,意味着你可以抛出任何异常。

4. C++中编译时不会检查函数的异常抛出列表。这意味着你在编写C++程序时,如果在函数中抛出了没有在异常抛出列表中声明的异常,编译时是不会报错的。而在java中,eclipse的提示功能真的好强大啊!

5. 在java中,抛出的异常都要是一个异常类;但是在C++中,你可以抛出任何类型,你甚至可以抛出一个整型。(当然,在C++中如果你catch中接收时使用的是对象,而不是引用的话,那么你抛出的对象必须要是能够复制的。这是语言的要求,不是异常处理的要求)。

6. 在C++中是没有finally关键字的。而java和python中都是有finally关键字的。

异常的基本语法

1. 抛出和捕获异常

很简单,抛出异常用throw,捕获用try……catch。

        C++ 异常处理涉及到三个关键字:try、catch、throw

  • throw: 使用 throw 关键字,当程序出现问题时会抛出一个异常。使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
  • 函数和函数可能抛出的异常集合作为函数声明的一部分是有价值的,例如

    void f(int a) throw (x2,x3); 
      

    表示f()只能抛出两个异常x2,x3,以及这些类型派生的异常,但不会抛出其他异常。如果f函数违反了这个规定,抛出了x2,x3之外的异常,例如x4,那么当函数f抛出x4异常时,
    会转换为一个std::unexpected()调用,默认是调用std::terminate(),通常是调用abort()。

    如果函数不带异常描述,那么假定他可能抛出任何异常。例如:

    int f();  //可能抛出任何异常 
      

    不带任何异常的函数可以用空表表示:

    int g() throw (); // 不会抛出任何异常 
      
    捕获异常
  • 捕获异常的代码一般如下:

    try {
        throw E();
    }
    catch (H h) {
         //何时我们可以能到这里呢
    } 
      

    1.如果H和E是相同的类型

    2.如果H是E的基类

    3.如果H和E都是指针类型,而且1或者2对它们所引用的类型成立

    4.如果H和E都是引用类型,而且1或者2对H所引用的类型成立

    从原则上来说,异常在抛出时被复制,我们最后捕获的异常只是原始异常的一个副本,所以我们不应该抛出一个不允许抛出一个不允许复制的异常。

    此外,我们可以在用于捕获异常的类型加上const,就像我们可以给函数加上const一样,限制我们,不能去修改捕捉到的那个异常。

    还有,捕获异常时如果H和E不是引用类型或者指针类型,而且H是E的基类,那么h对象其实就是H h = E(),最后捕获的异常对象h会丢失E的附加携带信息。

try
{
   // 保护代码
}catch( ExceptionName e1 )
{
   // catch 块
}catch( ExceptionName e2 )
{
   // catch 块
}catch( ExceptionName eN )
{
   // catch 块
}
}catch(...) //三个点代表捕获所有异常
{
   // catch 块
}
double division(int a, int b)
{
    if( b == 0 )
    {
        throw "Division by zero condition!";
        cout << "throw 后面的语句不再执行" <<endl;
    }
    return (a/b);
}
int main(void)
{
    try
    {
        division(5, 0);
    }
    catch(const char* error)
    {
        cout << error <<endl;
    }
    return 0;
}
#include <iostream>
using namespace std;
int main(void)
{
    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;  
    }  
    return 0;
}

说明:

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)     标准库异常类定义在头文件中

重新抛出:

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

异常规格说明(exception specification):

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

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

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

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

3)        注意:一般只会使用throw()来说明一个函数是安全的,不会抛出任何异常,这样编译器就可以对调

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值