标签:
使用异常处理,程序中独立开发的各部分能够就程序执行期间出现的问题相互通信,并处理这些问题。程序的一个部分能够检测出本部分无法解决的问题,这个问题检测部分可以将问题传递给准备处理问题的其它部分。
?
C++的异常处理中,需要由问题检测部分抛出一个对象给处理代码部分,通过这个对象的类型和内容,两个部分能够就出现了什么错误进行通信。
?
C++的异常处理中包括:
?
异常由throw表达式抛出异常对象而引发(raise),最后由catch子句捕获。异常对象这种抛出和捕获过程类似于将实参传递给函数的方式,可以简单将throw表示式认为是函数调用,并将所有catch子句认为是多态的函数声明。而具体调用到哪个catch子句,由throw传递的实参类型和catch声明的形参类型匹配决定。同时在普通函数的调用-声明模型都可以使用于throw-catch模式,包括参数传递、实参的作用范围、派生类转换为基类等语法都适用。
?
throw表达式是在try块中执行,并且当执行throw表达式的时候,不会执行跟在throw后面的语句,而是将控制流从throw转移到匹配的catch子句,该catch可以是同一函数中局部的catch,也可以在直接或间接调用发生异常的函数的另一个函数中,控制从一个地方传到另一个地方。
可以通过throw抛出任意类型的异常对象,包括对象、指针、引用和智能指针等类型。
可以通过智能指针对异常类型进行封装,从而在其他地方也能够正常捕捉。
?
其中在throw-catch过程中需要注意的问题:
可以采用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()); }???? |
?
catch子句的中的异常说明符看起来像只包含一个形参形参表,异常说明符是在其后跟一个(可选)形参名的类型名。
????说明符的类型决定了处理代码能够捕获的异常种类。类型必须是完全类型,即必须是内置类型或者是已经定义的程序员自定义类型。
在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个catch,相反,将选中第一个找到的可以处理该异常的catch。因此,在catch子句列表中,最特殊的catch必须最先出现。
异常与catch异常说明符匹配的规则比匹配实参和形参类型的规则更严格,大多数转换都不允许——除了下面几种可能的区别之处,异常的类型与catch说明符的类型必须完全匹配:
像形参声明一样,基类的异常说明符可以用于捕获派生类型的异常对象,即可以由try块中抛出派生类型,在catch生明为基类,从而能够接收这种转换。
?
有可能单个catch不能完全处理一个异常。在进行了一些校正行动之后,catch可能确定该异常必须由函数调用链中更上层的函数来处理,catch可以通过重新抛出(rethrow)将异常传递给函数调用链中更上层的函数。重新抛出是后面不跟类型或表达式的一个throw:
throw; |
????虽然重新抛出不指定自己的异常,但仍然将一个异常对象沿链向上传递,被抛出的异常是原来的异常对象,而不是catch形参。并且在同一个try块中的后面catch不会捕捉到重新抛出的异常对象。
????一般而言,catch可以改变它的形参。在改变它的形参之后,如果catch重新抛出异常,那么,只有单异常说明符是引用或指针类型的时候,才会传播那些改变。即只有指针类型的形参才能发生更改原来的异常对象。
?
即使函数不能处理被抛出的异常,它也可能想要在随抛出异常退出之前执行一些动作。所以c++提供一种机制,捕获所有异常的catch子句。捕获所有异常的catch子句形式为(…)。例如:
void manip() { try{ } catch(…) { throw } } |
注意:
????catch(…)子句可以单独使用,也可以与其它catch子句结合使用,但是若与其它catch子句结合使用,它必须是最后一个。
?
抛出异常的时候,将暂停当前函数的执行,开始查找匹配的catch子句。首先检查throw本身是否在try块内部,如果是,检查与该try相关的catch子句,看是否其中之一与被抛出对象相匹配。如果找到匹配的catch,就处理异常;如果找不到,就退出当前函数(释放当前函数的内存并撤销局部对象),并且继续在调用函数中查找。
????异常栈开展是指异常对象的查找catch的过程,它沿着嵌套函数调用链继续向上,直至为异常找到一个catch子句。只要找到能够处理异常的catch子句,就进入该catch子句,并在该处理代码中继续执行。当catch结束的时候,在紧接在与该try块相关的最后一个catch子句之后的点继续执行,意思是说当catch捕捉到异常后,则从try-catch块后的其它语句开始执行。
如果在栈展开的过程中,如果找不到匹配的catch子句,那么程序就调用库函数terminate,从而终止程序的执行。
由于当发生异常时,会提早退出程序,特别是当前函数不能处理抛出的异常对象时,那么执行过程将偏离正常的执行过程。此时,栈空间的对象都能够进行自动释放,而堆空间是需要手动进行释放,所以必须做相应的处理操作。
在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库terminate函数。一般而言,terminate函数将调用abort函数,强制从整个程序非正常退出。即在析构函数中不能抛出异常,若抛出异常将导致程序非正常退出。
与析构函数不同,构造函数内部所做的事情经常会抛出异常。如果在构造函数对象的时候发生异常,则该对象可能只是部分被构造,它的一些成员可能已经初始化,而另一些成员在异常发生之前还没有初始化。及时对象至少四部分被构造了,也要保证将会适当地撤销已构造的成员。
?
查看普通函数声明的时候,不可能确定该函数会抛出什么异常。但是,为了编写适当的catch子句,了解函数是否抛出异常以及会抛出哪种异常是很有用的。异常说明(exception specification)指定,如果函数抛出异常,被抛出的异常将包含在该说明中的一种,或者是从列出的异常中派生的类型。
异常说明跟在函数形参表之后,由一个异常说明在关键字throw之后跟着一个由园括号括住的异常类型列表,如下所示:
void recoup(int) throw (runtime_error); |
这个声明指出,recoup是接收int值的函数,并返回void如果recoup抛出一个异常,该异常将是runtime_error对象,或者是由runtime_error派生的类型的异常。 |
注意:
void no_problem() throw(); |
?
但是,不可能在编译时知道程序是否抛出异常以及会抛出哪些异常,只有在运行时才能检测是否违反函数异常说明。
如果抛出抛出了没有在其异常说明列出的异常,就调用标准库函数unexpected。默认情况下,unexpected函数调用terminate函数,terminate函数一般会终止程序。
?
基类中虚函数的异常说明,可以与派生类中对应虚函数的异常说明不同。但是派生类中虚函数的异常说明在基类中的异常说明必须都有,即基类虚函数的异常声明比派生类虚函数的异常声明要多。
?
boost::exception库针对标准库中异常类的缺陷进行了强化,提供<<操作符重载,可以向异常传入任意数据,有助于增加异常的信息和表达力。
其中exception位于名字空间boost,为使用exception,需要包含头文件<boost/exception/all.hpp>。
boost的exception库提供了两个基本类:exception和error_info;
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); |
?
exception类定义了友元函数operator<<,该函数是个自由函数,不是任何类的成员函数。其功能是将error_info<Tag, T>的对象输入到exception对象中。
该函数也是自由函数,其功能是从exception类型的E对象中获得写入的error_info<Tag, T>对象值,注意是T类型值,不是输入的error_info<Tag, T>对象。
?
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(); } |
?
?
boost的异常使用非常简单,只需继承boost::exception抽象类,并创建继承的异常对象;接着创建error_info对象;然后将error_info对象输入自定义异常对象中;最后抛出自定义异常对象。
?
因为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(); }; |
?
由于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; |
?
当发生异常时,可以创建一个自定义异常类,并用<<操作符向它存储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 |
?
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; |
?
若要使用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; } } |
?
C++抛出异常,使用throw表达;而且对原来存在的标准异常std::exception需使用enable_error_info()函数进行封装。为了方便抛出异常,boost库提供了两种方式封装throw表达式:
该函数用于简化enable_error_info()函数的调用,同时可以代替原始的throw语句来抛出异常。会自动使用enable_error_info()来包装异常对象,而且支持线程安全,比直接使用throw更好,例如:
throw_exception(std::runtime_error("runtime")); |
相当于: throw ( boost::enable_error_info((std::runtime_error("runtime")) ) |
?
在throw_exceptio()函数的基础上,boost又提供这个非常有用的宏BOOST_THROW_EXCEPTION(),它调用了boost::throw_exception()和enable_error_info(),因而可以接受任意的异常类型,同时又使用throw_function、throw_file和throw_line自动向异常添加了发送异常的函数名、文件名和行号等信息。
?
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