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

异常处理

时间:2016-04-23 00:03:41      阅读:234      评论: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的智能指针进行封装异常指针类型,从而实现自动释放,如下所示:

    1. try
    2. {
    3. ????bad_alloc *e = new bad_alloc();
    4. ????throw(shared_ptr<bad_alloc> (e));
    5. } catch (shared_ptr<bad_alloc> e)
    6. {
    7. ????printf("hello:%s\n", e->what());
    8. }

    ?

  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子句形式为(…)。例如:

    1. void manip()
    2. {
    3. ??? ?try{
    4. ???? }
    5. ???? catch(…)
    6. ??? {
    7. ??????? throw
    8. ??? }
    9. }

    ?

    注意:

    ????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派生的类型的异常。

    注意:

  • 空说明列表支出函数不抛出任何异常,如下所示:
  1. 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不会被误用,其类摘要如下:

    1. class exception
    2. {
    3. protected:
    4. ???exception();
    5. ???exception(exception const &x);
    6. ???virtual ~exception();
    7. private:
    8. ???template <class E, class Tag, class T>
    9. ???friend E const &operator<<(E const &, error_info<Tag, T> const &);
    10. }
    11. ?
    12. 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的异常处理信息是以该结构为介质。

    1. template <class Tag, class T>
    2. class error_info
    3. {
    4. public:
    5. ???typedef T value_type;
    6. ???error_info(value_type const &v);
    7. ???value_type &value();
    8. }

    ?

  • 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完成了。如下所示:

    1. #include <exception>
    2. #include <boost/exception/all.hpp>
    3. ?
    4. class myException: virtual public std::exception, virtual public boost::exception
    5. {
    6. public:
    7. ???myBootstException();
    8. };

    ?

  3. 创建信息对象

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

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

    1. typedef boost::error_info<struct tag_err_no, int> err_no;
    2. typedef boost::error_info<struct tag_err_str, string> err_str;

    ?

  4. 简单实例

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

    ?

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

    1. #include <boost/exception/all.hpp>
    2. using namespace boost;
    3. ?
    4. int main()
    5. {
    6. try
    7. ???{
    8. ????????throw myException() << err_no(123456);
    9. ???} catch (myException &e)
    10. ???{
    11. ??????cout << *get_error_info< err_no > (e) << endl;
    12. ???}
    13. }

    输出:

    1. 123456

    ?

  5. 错误信息类

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

    1. typedef error_info<struct errinfo_api_function_,char const *> errinfo_api_function;
    2. typedef error_info<struct errinfo_at_line_,int> errinfo_at_line;
    3. typedef error_info<struct errinfo_errno_,int> errinfo_errno;
    4. typedef error_info<struct errinfo_file_handle_,weak_ptr<FILE> > errinfo_file_handle;
    5. typedef error_info<struct errinfo_file_name_,std::string> errinfo_file_name;
    6. typedef error_info<struct errinfo_file_open_mode_,std::string> errinfo_file_open_mode;
    7. 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()的用法实例:

    1. #include <boost/execption/all.hpp>
    2. using namespace boost;
    3. ?
    4. struct my_err();
    5. int main()
    6. {
    7. try
    8. {
    9. ?????????throw enable_error_info(my_err())<<errinfo_errno(10);
    10. }
    11. catch(boost::exception &e)
    12. {
    13. ?????????cout<<*get_error_info(errrinfo_errno)(e)<<endl;
    14. }
    15. }

    ?

  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一起使用的示例:

    1. void boostException()
    2. {
    3. ???try
    4. ???{
    5. ??????BOOST_THROW_EXCEPTION( myBootstException () <<errinfo_api_function("api_function") );
    6. ???} catch (myBootstException &e)
    7. ???{
    8. ??????cout << diagnostic_information(e) << endl;
    9. ???}
    10. }

    输出:

    1. ../src/boost.cpp(45): Throw in function void boostException()
    2. Dynamic exception type: boost::exception_detail::clone_impl<myBootstException>
    3. std::exception::what: std::exception
    4. [boost::errinfo_api_function_*] = api_function

    ?

异常处理

标签:

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

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