zoukankan      html  css  js  c++  java
  • C++_异常9-异常的注意事项

    一、先讨论异常被引发后,可能导致的问题

    意外异常

      如果它是在带异常规范的函数中引发的,则必须与规范列表中的某种异常匹配,否则为意外异常。在默认情况下,这将导致程序异常终止(虽然C++11摒弃了异常规范,但仍支持它,且有些现有的代码使用了它)。

    未捕获异常

      如果异常不是在函数中引发的,则必须捕获它。如果没被捕获(在没有try块或没有匹配的catch块时,将出现这种情况),则异常被称为未捕获异常。

    在默认情况下,这两种异常将导致程序异常终止。当然可以修改程序对意外异常和未捕获异常的反应。

    ==========================================================================

    未捕获异常不会导致程序立刻异常终止。相反,程序将首先调用函数terminate()。在默认的情况下,terminate()调用abort()函数。

    可以指定terminate()应调用的函数来修改terminate()的这种行为。

    为此,可以调用set_terminate()函数。

    set_terminate()和terminate()都是在头文件exception中声明的。

    typedef void (* terminate_handler) ();

    terminate_handler set_terminate(terminate_handler f) throw();  //C++98

    terminate_handler set_terminate(terminate_handler f) noexcept;    //C++11

    void terminate();                   //C++98

    void terminate() noexcept;  //C++11

    假设希望未捕获的异常导致程序打印一条消息,然后调用exit()函数,将退出状态值设置为5。

    首先,请包含头文件exception。可以使用using编译指令、适当的using声明或std::限定符,来使其声明可用。

    #include <exception>

    using namespace std;

    然后,设计一个完成上述两种操作所需的函数,其原型如下:

    void myQuit()

    {

        cout<<"Terminating due to uncaught exception ";

        exit(5);

    }

    最后,在程序的开头,将终止操作指定为调用该函数

    set_terminate(myQuit);

    现在,如果引发了一个异常且没有被捕获,程序将调用terminate(),而后者将调用MyQuit()。

    ==========================================================================

    接下来看一下意外异常

    通过给函数指定异常规范,可以让函数的用户知道要捕获哪些异常。

    假设函数的原型如下:

    double Argh() throw(out_if_bounds);

    则可以这样使用该函数:

    try {

        x = Argh(a,b);

    }

    catch(out_of_bounds & ex)

    {

    ...

    }

    知道应捕获哪些异常很有帮助,因为默认情况下,未捕获的异常将导致程序异常终止。

    原则上,异常规范应包含函数调用的其他函数引发的异常。例如,如果Argh()调用了Duh()函数,而后者可能引发retort对象异常,则Argh()和Duh()的异常规范中斗应包含retort。

    除非自己编写所有的函数,并且特别仔细。否则无法保证上述工作都已正确完成。

    所以这也表明异常规范机制处理起来比较麻烦,这也是C++11将其摒弃的原因之一。

      在这种情况之下,行为与未捕获异常极其相似。如果发生意外异常,程序将调用unexpected()函数,后者在默认情况下将调用abort()。

    正如有一个可用于修改terminate()的行为的set_terminate()函数一样。

    也有一个可用于修改unexpected()行为的set_unexpected()函数。这些新函数也是在头文件中exception中声明的:
    typedef void(* unexpected_handler) ();

    unexpected_handler set_unexpected(unexpected_handler f) throw();   //C++98

    unexpected_handler set_unexpected(unexpected_handler f) noexcept;    //C++11

    void unexpected();                       //C++98

    void unexpected() noexcepted;    //C+0X

    然而,与提供给set_terminate()函数的行为相比,提供给set_unexpected()的函数的行为受到更严格的限制。具体地说,unexpected_handler函数可以:

    1、通过调用terminate()(默认行为)、abort()或exit()来终止程序;

    2、引发异常。

    引发异常的结果取决于unexpected_handler函数所引发的异常以及引发意外异常的函数的异常规范;

    1、如果新引发的异常与原来的异常规范匹配,则程序将从那里开始进行正常处理,即寻找与新引发异常匹配的catch块。基本上,这种方法将用预期的异常取代意外异常。

    2、如果新引发的异常与原来的异常规范不匹配,且异常规范中没有包括std::bad_exception类型,则程序将调用terminate()、bad_exception是从exception派生而来的,其声明位于头文件exception中。

    3、如果新引发的异常与原来的异常规范不匹配,且异常规范中包括std::bad_exception类型,则不匹配的异常将被std::bad_exception异常所取代。

    总之,要捕获所有的异常(不管是预期的异常还是意外异常),则可以这样做:

    首先确保异常头文件的声明可用:

    #include <exception>

    using namespace std;

    然后,设计一个替代函数,将意外异常转换为bad_exception异常,该函数的原型如下:

    void myUnexpected()

    {

        throw std::bad_exception();

    }

    仅使用throw,而不指定异常将导致重新引发原来的异常。然而,如果异常规范中包含了这种类型,则该异常将被bad_exception对象所取代。

    接下来在程序的开始位置,将意外异常操作指定为调用该函数:

    set_unexpected(myUnexpected);

    最后,将bad_exception类型包括在异常规范中,并添加如下catch块序列:

    double Argh() throw(out_of_bounds, bad_exception);

    ...

    try {

        x = Argh(a,b);

    }

    catch(out_of_bounds & ex)

    {

    ...

    }

    catch(bad_exception & ex)

    {

    ...

    }

     

    二、有关异常的注意事项

    从前面关于如何使用异常的讨论可知,应在设计程序时就加入异常处理功能,而不是以后再添加。

    这样做有些缺点。

    例如,使用异常会增加程序代码,降低程序的运行速度。

    异常规范不适用于模板,因为模板函数引发的异常可能随特定的具体化而异。

    异常和动态内存分配并非总能协同工作。

    下面进一步讨论动态内存分配和异常。

    void test1(int n)

    {

        string mesg("I'm trapped in an endless loop");

        ...

        if (oh_no)

            throw exception();

        return;

    }

    string类采用动态内存分配。通常,当函数结束时,将为mesg调用string的析构函数。

    虽然throw语句过早地终止了函数。但它仍然使得析构函数被调用,这要归功于栈解退。

    因此在这里,内存被正确地管理。

    接下来看这个函数:

    void test2(int n)

    {

        double * ar = new double[n];

        ....

        if (oh_no)

            throw exception();

        ...

        delete [] ar;

        return;

    }

    这里有个问题,解退栈的时候,将删除栈中的变量ar。但函数过早地终止意味着函数末尾的delete[]语句被忽略。

    指针消失了,但它指向的内存块未被释放,并且不可访问。总之,这些内存被泄漏了。

    当然这种泄漏是可以被避免的。例如,可以再引发异常的函数中捕获该异常,在catch块中包含一些清理代码,然后重新引发异常:

    void test3(int n)

    {

        double * ar = new double[n];

        try {

        }

        catch (exception & ex)

        {

            delete [] ar;

            throw;

        }

        ...

        delete [] ar;

        return;

    }

    但是这样做,仍然会增加疏忽和产生其他错误的机会。另一种解决方法是使用智能指针模板

    总之,虽然异常处理对于某些项目极为重要,但它也会增加编程的工作量,增大程序,降低程序的速度。

    另一方面,不进行错误检查的代价可能非常高。

     ======================================================

    三、异常处理

    现代库中,异常处理的复杂程度可能再创新高。

    理解库中的异常处理像学习语言本身一样困难。

    现代库中包含的例程和模式可能像C++语法细节一样陌生而困难。

    要开发出优秀的软件,必须花时间了解库和类中的复杂内容,就像必须花时间学习C++本身一样。

    通过库文档和源代码了解到的异常和错误处理细节将使程序员和他的软件受益。

  • 相关阅读:
    fatfs输出目录
    《基于多光程长的高散射物质光学参数的测量及其应用》论文
    《2013李永乐线性代数强化班》视频1,2,3,4
    oled屏幕模块
    python中数据结构
    大数据python词频统计之hdfs分发-cacheFile
    8大排序之Python实现 冒泡排序优化
    大数据python词频统计之本地分发-file
    2019-04-30vmware虚拟机安装macos 10.8格式为iso
    2019-04-24Scurecrt 如何下载文本文件
  • 原文地址:https://www.cnblogs.com/grooovvve/p/10435135.html
Copyright © 2011-2022 走看看