码迷,mamicode.com
首页 > 其他好文 > 详细

异常处理

时间:2016-04-22 23:40:07      阅读:273      评论:0      收藏:0      [点我收藏+]

标签:

  1. C++ 异常处理

    使用异常处理,程序中独立开发的各部分能够就程序执行期间出现的问题相互通信,并处理这些问题。程序的一个部分能够检测出本部分无法解决的问题,这个问题检测部分可以将问题传递给准备处理问题的其它部分。

    ?

  2. 处理基本结构

    C++的异常处理中,需要由问题检测部分抛出一个对象给处理代码部分,通过这个对象的类型和内容,两个部分能够就出现了什么错误进行通信。

    ?

    C++的异常处理中包括:

  • throw表达式:错误检查部分使用这种表达式来说明遇到了不可处理的错误。可以说,throw引发(raise)了异常条件。
  • try块:错误处理部分使用它来处理异常。try语句块以try关键字开始,并以一个或多个catch子句结束。在try块中执行的代码所抛出(throw)的异常,通常会被其中一个catch字句处理。由于它们"处理"异常,catch子句也称为处理代码。
  • 异常类:由标准定义的一组异常类,用来在throw和相应的catch之间传递有关的错误信息。

?

  1. 抛出与捕获

    异常由throw表达式抛出异常对象而引发(raise),最后由catch子句捕获。异常对象这种抛出和捕获过程类似于将实参传递给函数的方式,可以简单将throw表示式认为是函数调用,并将所有catch子句认为是多态的函数声明。而具体调用到哪个catch子句,由throw传递的实参类型和catch声明的形参类型匹配决定。同时在普通函数的调用-声明模型都可以使用于throw-catch模式,包括参数传递、实参的作用范围、派生类转换为基类等语法都适用。

    ?

  2. throw

    throw表达式是在try块中执行,并且当执行throw表达式的时候,不会执行跟在throw后面的语句,而是将控制流从throw转移到匹配的catch子句,该catch可以是同一函数中局部的catch,也可以在直接或间接调用发生异常的函数的另一个函数中,控制从一个地方传到另一个地方。

    可以通过throw抛出任意类型的异常对象,包括对象、指针、引用和智能指针等类型。

    可以通过智能指针对异常类型进行封装,从而在其他地方也能够正常捕捉。

    ?

    其中在throw-catch过程中需要注意的问题:

    1. 若传递对象类型,那么被传递的异常类型必须可复制的类型。
    2. 若传递指针类型,那么需保证该指针所指向的对象仍有效,若是栈对象会在退出try块后被自动释放;若是堆对象,那么需要手动释放。

    可以采用boost的智能指针进行封装异常指针类型,从而实现自动释放,如下所示:

    try

    {

    bad_alloc *e = new bad_alloc();

    throw(shared_ptr<bad_alloc> (e));

    } catch (shared_ptr<bad_alloc> e)

    {

    printf("hello:%s\n", e->what());

    }????

    ?

  3. catch

    catch子句的中的异常说明符看起来像只包含一个形参形参表,异常说明符是在其后跟一个(可选)形参名的类型名。

    ????说明符的类型决定了处理代码能够捕获的异常种类。类型必须是完全类型,即必须是内置类型或者是已经定义的程序员自定义类型。

    1. 查找匹配的处理代码

    在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个catch,相反,将选中第一个找到的可以处理该异常的catch。因此,在catch子句列表中,最特殊的catch必须最先出现。

    异常与catch异常说明符匹配的规则比匹配实参和形参类型的规则更严格,大多数转换都不允许——除了下面几种可能的区别之处,异常的类型与catch说明符的类型必须完全匹配:

  • 运行从const到const的转换。也就是说,非const对象的throw可以与指定接受const引用的catch匹配。
  • 允许从派生类型到基类类型的转换。
  • 将数组转换为指向数组类型的指针,将函数转换为指向数组类型的适当指针。
  1. 异常说明符与继承

像形参声明一样,基类的异常说明符可以用于捕获派生类型的异常对象,即可以由try块中抛出派生类型,在catch生明为基类,从而能够接收这种转换。

?

  1. rethrow

    有可能单个catch不能完全处理一个异常。在进行了一些校正行动之后,catch可能确定该异常必须由函数调用链中更上层的函数来处理,catch可以通过重新抛出(rethrow)将异常传递给函数调用链中更上层的函数。重新抛出是后面不跟类型或表达式的一个throw:

    throw

    ????虽然重新抛出不指定自己的异常,但仍然将一个异常对象沿链向上传递,被抛出的异常是原来的异常对象,而不是catch形参。并且在同一个try块中的后面catch不会捕捉到重新抛出的异常对象。

    ????一般而言,catch可以改变它的形参。在改变它的形参之后,如果catch重新抛出异常,那么,只有单异常说明符是引用或指针类型的时候,才会传播那些改变。即只有指针类型的形参才能发生更改原来的异常对象。

    ?

  2. catch(…)

    即使函数不能处理被抛出的异常,它也可能想要在随抛出异常退出之前执行一些动作。所以c++提供一种机制,捕获所有异常的catch子句。捕获所有异常的catch子句形式为(…)。例如:

    void manip()

    {

    try{

    }

    catch(…)

    {

    throw

    }

    }

    注意:

    ????catch(…)子句可以单独使用,也可以与其它catch子句结合使用,但是若与其它catch子句结合使用,它必须是最后一个。

    ?

  3. 异常栈展开

    抛出异常的时候,将暂停当前函数的执行,开始查找匹配的catch子句。首先检查throw本身是否在try块内部,如果是,检查与该try相关的catch子句,看是否其中之一与被抛出对象相匹配。如果找到匹配的catch,就处理异常;如果找不到,就退出当前函数(释放当前函数的内存并撤销局部对象),并且继续在调用函数中查找。

    ????异常栈开展是指异常对象的查找catch的过程,它沿着嵌套函数调用链继续向上,直至为异常找到一个catch子句。只要找到能够处理异常的catch子句,就进入该catch子句,并在该处理代码中继续执行。当catch结束的时候,在紧接在与该try块相关的最后一个catch子句之后的点继续执行,意思是说当catch捕捉到异常后,则从try-catch块后的其它语句开始执行。

    1. 未捕获的异常终止程序

    如果在栈展开的过程中,如果找不到匹配的catch子句,那么程序就调用库函数terminate,从而终止程序的执行。

    1. 内存空间释放

    由于当发生异常时,会提早退出程序,特别是当前函数不能处理抛出的异常对象时,那么执行过程将偏离正常的执行过程。此时,栈空间的对象都能够进行自动释放,而堆空间是需要手动进行释放,所以必须做相应的处理操作。

    1. 异常与析构函数

    在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库terminate函数。一般而言,terminate函数将调用abort函数,强制从整个程序非正常退出。即在析构函数中不能抛出异常,若抛出异常将导致程序非正常退出。

    1. 异常与构造函数

    与析构函数不同,构造函数内部所做的事情经常会抛出异常。如果在构造函数对象的时候发生异常,则该对象可能只是部分被构造,它的一些成员可能已经初始化,而另一些成员在异常发生之前还没有初始化。及时对象至少四部分被构造了,也要保证将会适当地撤销已构造的成员。

    ?

  4. 函数异常说明

    查看普通函数声明的时候,不可能确定该函数会抛出什么异常。但是,为了编写适当的catch子句,了解函数是否抛出异常以及会抛出哪种异常是很有用的。异常说明(exception specification)指定,如果函数抛出异常,被抛出的异常将包含在该说明中的一种,或者是从列出的异常中派生的类型。

    1. 定义异常说明

    异常说明跟在函数形参表之后,由一个异常说明在关键字throw之后跟着一个由园括号括住的异常类型列表,如下所示:

    void recoup(int) throw (runtime_error);

    这个声明指出,recoup是接收int值的函数,并返回void如果recoup抛出一个异常,该异常将是runtime_error对象,或者是由runtime_error派生的类型的异常。

    注意:

  • 空说明列表支出函数不抛出任何异常,如下所示:

void no_problem() throw();

  • 如果一个函数声明没有指定异常说明,则该函数可以抛出任意类型的异常。

?

  1. 违反异常说明

但是,不可能在编译时知道程序是否抛出异常以及会抛出哪些异常,只有在运行时才能检测是否违反函数异常说明。

如果抛出抛出了没有在其异常说明列出的异常,就调用标准库函数unexpected。默认情况下,unexpected函数调用terminate函数,terminate函数一般会终止程序。

?

  1. 虚函数异常说明

基类中虚函数的异常说明,可以与派生类中对应虚函数的异常说明不同。但是派生类中虚函数的异常说明在基类中的异常说明必须都有,即基类虚函数的异常声明比派生类虚函数的异常声明要多。

?

  1. boost异常处理

    boost::exception库针对标准库中异常类的缺陷进行了强化,提供<<操作符重载,可以向异常传入任意数据,有助于增加异常的信息和表达力。

    其中exception位于名字空间boost,为使用exception,需要包含头文件<boost/exception/all.hpp>。

  2. 类摘要

    boost的exception库提供了两个基本类:exception和error_info;

  3. exception类

    exception类几乎没有公开的成员函数,它的设计意图:它是一个抽象类,除了它的子类,任何人都不能创建或销毁它,这保证了exception不会被误用,其类摘要如下:

    class exception

    {

    protected:

    exception();

    exception(exception const &x);

    virtual ~exception();

    private:

    template <class E, class Tag, class T>

    friend E const &operator<<(E const &, error_info<Tag, T> const &);

    }

    ?

    typename T* get_error_info(E & x);

    ?

  • operator<<函数

    exception类定义了友元函数operator<<,该函数是个自由函数,不是任何类的成员函数。其功能是将error_info<Tag, T>的对象输入到exception对象中。

  • get_error_info函数

    该函数也是自由函数,其功能是从exception类型的E对象中获得写入的error_info<Tag, T>对象值,注意是T类型值,不是输入的error_info<Tag, T>对象。

?

  1. error_info类

    error_info类是exception对象的信息存储体,即boost的异常处理信息是以该结构为介质。

    template <class Tag, class T>

    class error_info

    {

    public:

    typedef T value_type;

    error_info(value_type const &v);

    value_type &value();

    }

    ?

  • error_info类的T类型是存储信息的类型;
  • error_info类的Tag类型是区分不同error_info类型的标识;
  • error_info类有一个构造函数,从而通过传递T类型的数组进行对象创建;
  • 在boost::exception对象中可以存储多个error_info对象,但是每种类型的error_info对象只能存放一个,而error_info的类型是由Tag来区分。

    ?

  1. 异常使用

    boost的异常使用非常简单,只需继承boost::exception抽象类,并创建继承的异常对象;接着创建error_info对象;然后将error_info对象输入自定义异常对象中;最后抛出自定义异常对象。

    ?

  2. 继承异常类

    因为boost::exception被定义为抽象类,因此我们的程序必须定义它的子类才能使用它,同时可以继承std::exception一起工作,从而获得两者的能力。但是C++进行多继承,最好使用虚拟继承的方式。

    通常,继承完成后自定义异常类的实现也就结束了,不需要"画蛇添足"地向它增加成员变化或者成员函数,这些工作都已经由boost::exception完成了。如下所示:

    #include <exception>

    #include <boost/exception/all.hpp>

    ?

    class myException: virtual public std::exception, virtual public boost::exception

    {

    public:

    ????myBootstException();

    };

    ?

  3. 创建信息对象

    由于boost::exception对象需要存储的信息是模板类error_info。因为error_info是一个模板类,所以在创建对象时,需要指定相应的类型;其中用一个struct作为第一个模板参数来标志信息类型,再用第二个模板参数指定信息的数据类型。

    由于error_info<>的类型定义较长,为了使用方便起见,通常需要使用typedef。如下所示:

    typedef boost::error_info<struct tag_err_no, int> err_no;

    typedef boost::error_info<struct tag_err_str, string> err_str;

    ?

  4. 简单实例

    当发生异常时,可以创建一个自定义异常类,并用<<操作符向它存储error_info类型的任意信息,这些信息可以在任何时候使用get_error_info()函数提取。

    ?

    如在上述自定义的myException类和宏定义的err_no对象基础上,进行如下的使用:

    #include <boost/exception/all.hpp>

    using namespace boost;

    ?

    int main()

    {

    try

    ????{

    throw myException() << err_no(123456);

    ????} catch (myException &e)

    ????{

    ????????cout << *get_error_info< err_no > (e) << endl;

    ????}

    }

    输出:

    123456

    ?

  5. 错误信息类

    error_info类型是异常对象的存储信息,如上述我们宏定义了err_no和err_str类型。其中boost也提供了若干个预先定义好的错误信息类,这样使得程序员使用起来更轻松:

    typedef error_info<struct errinfo_api_function_,char const *> errinfo_api_function;

    typedef error_info<struct errinfo_at_line_,int> errinfo_at_line;

    typedef error_info<struct errinfo_errno_,int> errinfo_errno;

    typedef error_info<struct errinfo_file_handle_,weak_ptr<FILE> > errinfo_file_handle;

    typedef error_info<struct errinfo_file_name_,std::string> errinfo_file_name;

    typedef error_info<struct errinfo_file_open_mode_,std::string> errinfo_file_open_mode;

    typedef error_info<struct errinfo_type_info_name_,std::string> errinfo_type_info_name;

    ?

  6. boost增强功能

  7. 包装标准异常

    若要使用boost的异常处理功能,需要继承boost::exception。boost库提供一个模板函数来封装标准异常类std::exception或其子类,从而能够不需手动继承boost::exception类就可使用boost的异常处理功能。该函数为:enable_error_info(T &e),它可以包装类型T,产生一个从boost::exception和T派生的类。

    如下是enable_error_info()的用法实例:

    #include <boost/execption/all.hpp>

    using namespace boost;

    ?

    struct my_err();

    int main()

    {

    try

    {

    throw enable_error_info(my_err())<<errinfo_errno(10);

    }

    catch(boost::exception &e)

    {

    cout<<*get_error_info(errrinfo_errno)(e)<<endl;

    }

    }

    ?

  8. 包装throw表达式

    C++抛出异常,使用throw表达;而且对原来存在的标准异常std::exception需使用enable_error_info()函数进行封装。为了方便抛出异常,boost库提供了两种方式封装throw表达式:

  9. throw_exception()

    该函数用于简化enable_error_info()函数的调用,同时可以代替原始的throw语句来抛出异常。会自动使用enable_error_info()来包装异常对象,而且支持线程安全,比直接使用throw更好,例如:

    throw_exception(std::runtime_error("runtime"));

    相当于:

    throw ( boost::enable_error_info((std::runtime_error("runtime")) )

    ?

  10. BOOST_THROW_EXCEPTION()

    在throw_exceptio()函数的基础上,boost又提供这个非常有用的宏BOOST_THROW_EXCEPTION(),它调用了boost::throw_exception()和enable_error_info(),因而可以接受任意的异常类型,同时又使用throw_function、throw_file和throw_line自动向异常添加了发送异常的函数名、文件名和行号等信息。

    ?

  11. 增强get_error_info函数

    boost::exception提供了方便存储信息的能力,可以向它添加任意数量的信息,但当异常对象被用operator<<多次追加数据时,会导致它存储有大量的信息。如果还是采用自由函数get_error_info()来逐项检索的话可能会很麻烦甚至不可能。这时boost库提供另一个函数:diagnostic_information()。

    diagnostic_information()可以输出异常对象包含的所有信息,如果异常是由宏BOOST_THROW_EXCEPTION抛出的,则可能相当多并且不是用户友好的,但对于程序开发者可以提供很好的诊断错误的信息。

    ?

    如下是BOOST_THROW_EXCEPTION配合diagnostic_information一起使用的示例:

    void boostException()

    {

    ????try

    ????{

    ????????BOOST_THROW_EXCEPTION( myBootstException () <<errinfo_api_function("api_function") );

    ????} catch (myBootstException &e)

    ????{

    ????????cout << diagnostic_information(e) << endl;

    ????}

    }

    输出:

    ../src/boost.cpp(45): Throw in function void boostException()

    Dynamic exception type: boost::exception_detail::clone_impl<myBootstException>

    std::exception::what: std::exception

    [boost::errinfo_api_function_*] = api_function

    ?

异常处理

标签:

原文地址:http://www.cnblogs.com/hlwfirst/p/5423080.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!