异常处理机制
c++中的异常体系剖析
1.异常处理机制的相关语法:
增强错误恢复能力是提高代码健壮性最有力的途径之一。异常处理作为c++的主要特征之一,是我们考虑问题和处理错误的一种更好的方式。关于异常处理的重要的三个关键字: try,catch,throw。将可能产生异常的代码写在try block中,并且在try block后面紧跟catch捕获器来捕获并处理异常。如果捕获后不处理异常就使用throw关键字再次将异常抛出。一个方法如果可能抛出异常,就可以使用声明抛出异常语法(exception specification)。这就是可选的异常规格说明,它是函数声明的修饰符,写在参数列表的后面
声明抛出异常有以下几种形式:
1.方法不抛出异常:
returnType functionName(parameterType parameter)throw();//returnType为函数返回类型,parameterType为参数类型
2.一个方法抛出一种或者多种异常:
returnType functionName(parameterType parameter)throw(exceptionType1,....exceptionTypen);//exceptionType为可能抛出的数据类型
3.一个方法抛出任何类型的异常:
returnType functionName(parameterType parameter);//定义和普通方法一样表示可以抛出任何数据类型的异常
声明抛出异常语法的特别之处:
1.虽然在方法上通过throw关键字声明不抛出任何异常,但是如果此方法有异常产生不进行处理并抛出,那么异常还是能够抛出。如果此方法调用者内有匹配的异常捕获器,那么异常还是能够被捕获并处理。但是请注意:如果在方法上通过noexcept声明不抛出任何异常,那么此方法即使有异常产生并抛出,但异常也绝对不能被抛出,即使在任何层次上有异常捕获器匹配该异常,也绝对不能被捕获处理,程序会最终将调用abort函数中止程序正常运行
2.虽然在方法上通过throw关键字声明抛出一种或者多种类型异常,但是如果方法体内还有其他类型的异常(不在声明的抛出类型异常内,违反了异常规格说明)产生不进行处理并抛出,那么异常也能够抛出。只要有任何一个层次的异常捕获器匹配该异常,那么该异常就能够被捕获并处理。这种特殊的情况是在vs下的vc编译器产生的,如果开发环境的编译器是g++编译器,那么如果方法体内还有其他类型的异常(不在声明的抛出类型异常内)产生不进行处理并抛出,那么异常绝对不能够抛出。全局unexpected函数将被调用。默认的unexpected函数将调用terminate函数,当然也可以通过set_unexpected函数自定义函数替换默认的unexpected函数。如果在unexpected函数中抛出的异常类型不再违反触发unexpected的函数的异常规格说明,那么程序将恢复到这个函数被调用的位置重新开始异常匹配。(这是unexpected( )函数特有的行为。)
**
例如:
//编译器为g++编译器
#include <iostream>
using namespace std;
//定义自定义的unexpected函数
void myUnexpected()
{
cout << "这是我自己的unexpected函数" << endl;
//这里只能抛出text方法声明的int类型异常,如果有匹配的
//异常捕获处理器就捕获并处理。但是如果抛出其他类型的
//异常,即使有匹配的异常处理器也不能被捕获并处理。程序
//将调用terminate函数中止程序的运行。
throw 1;
//因为在自定义的unexpected函数中抛出的异常类型不再违反触发此函数的函数的异常规格说明,
//所以程序将恢复到调用test函数的位置重新开始匹配异常
}
void text()throw(int) //抛出异常声明此方法只抛出int类型的异常
{
throw'a'; //方法体内却抛出char类型的异常,那么能否抛出呢?
}
int main()
{
set_unexpected(myUnexpected);
try
{
text();
}
catch(char)
{
cout << "char类型异常" << endl;
}
catch(int)
{
cout << "int类型异常" << endl;
}
catch(...)
{
cout << "未知类型异常" << endl;
}
cout << "HelloWorld" << endl;
return 0;
}
程序运行结果如下:
如下部分源码选自gcc中的g++编译器中:
/// If you write a replacement %unexpected handler, it must be of this type.
typedef void (*unexpected_handler) ();
这里声明一个函数指针类型。如果你通过set_unexpected函数设置自己的unexpected函数,那么该函数的参数也是一个这样的函数指针类型。
#if __cplusplus >= 201103L
#endif
/** The runtime will call this function if %exception handling must be
* abandoned for any reason. It can also be called by the user. */
void terminate() noexcept __attribute__ ((__noreturn__));
/// Takes a new handler function as an argument, returns the old function
///返回先前unexpected函数的函数指针,以便以后恢复使用之前的unexpected函数.
unexpected_handler set_unexpected(unexpected_handler) noexcept;
#if __cplusplus >= 201103L
/// Return the current unexpected handler.
unexpected_handler get_unexpected() noexcept;//得到当前的函数指针
#endif
/** The runtime will call this function if an %exception is thrown which
* violates the function's %exception specification. */
void unexpected() __attribute__ ((__noreturn__));
//这里应该就是void unexpected()nothrow{abort();}
#include <iostream>
#include <exception>
using namespace std;
void myUnexpected()
{
cout << "自定义unexpectecd函数调用" << endl;
exit(0); //正常退出程序,如果调用底层库的unexpected函数就会调用
//abort函数中止程序
}
void test()throw(int)
{
throw 'a';
}
void example1()noexcept
{
set_unexpected(myUnexpected);
unexpected_handler handler = get_unexpected(); //得到当前的函数指针
cout << handler <<endl; //1
cout << myUnexpected << endl; //1,the address of myUnexpected will evaluate as true
try
{
test();
}
catch(int)
{
cout << "int类型被捕获" << endl;
}
catch(char)
{
cout << "char类型被捕获" << endl;
}
}
void example2()noexcept
{
unexpected_handler handler = set_unexpected(myUnexpected);
set_unexpected(handler); //恢复以前的unexpected函数
try
{
test();
}
catch(int)
{
cout << "int类型被捕获" << endl;
}
catch(char)
{
cout << "char类型被捕获" << endl;
}
}
int main()
{
//example1(); //自定义unexpectecd函数调用,正常退出程序
example2(); //terminate called after throwing an instance of 'char'
return 0;
}
如果一个程序中有异常产生并且没有匹配的异常捕获处理器捕获处理,那么terminate函数将被调用,默认的terminate函数将调用abort函数中止程序的运行。当然我们也可以通过set_terminate函数自定义terminate函数
/// If you write a replacement %terminate handler, it must be of this type.
typedef void (*terminate_handler) ();
这里声明一个函数指针类型。如果你通过set_terminate函数设置自己的terminate函数,那么该函数的参数也是一个这样的函数指针类型。
/// Takes a new handler function as an argument, returns the old function.
terminate_handler set_terminate(terminate_handler) noexcept;
#if __cplusplus >= 201103L
/// Return the current terminate handler.
terminate_handler get_terminate() noexcept;
#endif
/** The runtime will call this function if %exception handling must be
* abandoned for any reason. It can also be called by the user. */
void terminate() _GLIBCXX_USE_NOEXCEPT __attribute__ ((__noreturn__));
////这里应该就是void terminate()nothrow{abort();}
简单示例如下:
#include <iostream>
using namespace std;
//一般来说,我们会在自定义的set_terminate函数中将异常出错信息记录在日志中
//下面仅做测试
void myTerminate_handler()
{
cout << "这里测试set_terminate函数" << endl;
exit(0); //正常退出程序
}
void test()
{
throw 100;
}
int main()
{
set_terminate(myTerminate_handler);
test(); //该函数抛出异常,但是没有匹配的异常处理器捕获处理
return 0;
}
既然有抛出异常声明语法,那么当我们定义一个函数时,如果要让此函数绝对不抛出任何异常,就应该使用noexcept关键字声明。
示例如下:
//编译器为vc编译器
# include <iostream>
void testNoThrow()throw()
{
throw("虽然函数声明不抛出任何类型异常,但是函数体内抛出const char *类型异常");
}
void testOptionalThrow()throw(char)
{
throw 100;
}
int main()
{
try
{
testNoThrow();
}
catch (const char* msg)
{
std::cout << msg << std::endl; //打印结果为虽然函数声明不抛出任何类型异常,但是函数体内抛出const char *类型异常
}
try
{
testOptionalThrow();
}
catch (int val)
{
std::cout << val << std::endl; //打印结果为100
}
return 0;
}
那么这是为什么呢?我们可以这样理解:vc编译器不会对异常声明语法进行检测,异常声明更多的是给函数的用户看,让用户知道如何捕获这个函数可能抛出的异常,仅供用户参考。
一个方法通过throw关键字抛出异常后,一般交由此方法调用者捕获并处理异常。此方法调用者的catch捕获器中会匹配与抛出异常类型对象类型一致的异常类型, 然后进行异常捕获并处理。如果不处理就使用throw关键字继续将异常往上抛等待匹配的捕获器捕获并处理。如果没有任何一个层次的捕获器匹配,则位于exception头文件的terminate函数将被调用,默认的terminate函数将调用abort函数终止程序的运行。
一个简单语法的示例:
#include <iostream>
int divide(int x, int y)throw(int) //声明只抛出int类型异常
{
if (y == 0)
{
throw x; //抛出异常即抛出一个对象
}
else
{
return x / y;
}
}
void interDivide(int x, int y)
{
//将可能产生异常的代码写在try语句块中
try
{
divide(x, y);
}
catch (int val)
{
throw val; //本方法的捕获器虽然捕获了异常,但是不处理继续往上抛
}
}
int main()
{
////
////try
////{
//// std::cout << divide(100,50) << std::endl;
//// std::cout << divide(100,0) << std::endl;
////}
////catch (int val)
////{
//// std::cout << val << "除零异常" << std::endl;
////}
////catch (...)
////{
//// std::cout << "未知异常" << std::endl;
////}
try
{
interDivide(100,0);
}
catch (int val) //此方法的捕获器捕获异常并进行处理
{
std::cout << val << "除零异常" << std::endl;
}
catch (...) //万能捕获器可以捕获任何类型的异常,如果前面的
{ //捕获器没有捕获异常,则此捕获器就捕获异常
std::cout << "未知异常" << std::endl;
}
return 0;
}
stack unwinding:栈解旋亦称栈反解。在使用throw关键字抛出一个异常时,可以抛出任意数据类型包括用户自定义类型的对象。在异常发生之前抛出异常所在函数创建的局部变量和try语句块中创建的局部变量将自动销毁。
# include <iostream>
class Example
{
private:
int val;
public:
Example(int val) :val(val)
{
}
~Example()
{
std::cout << "The destructor is called" << std::endl;
}
};
void test()throw(const char*)
{
Example ex1(100);
throw "这个案例测试栈解旋特性";
}
int main()
{
try
{
test();
}
catch (const char* mess)
{
std::cout << "const char *类型异常被捕获" << std::endl;
}
catch (...)
{
std::cout << "未知类型异常" << std::endl;
}
return 0;
}
运行结果如下:
2.异常继承体系
c++中异常继承体系:
通过标准库产生的所有异常都直接或者间接的继承于exception类, exception类的直接子类logic_error,bad_optional_access,runtime_error,bad_typeid,bad_cast,bad_weak_ptr,bad_function_call,bad_alloc,bad_exception,ios_base::failure,bad_variant_access.*
品读几个主要的异常类:
1.exception类:Base class for all library exceptions.This is the base class for all exceptions thrown by the standard library, and by certain language expressions. You are free to derive your own exception classes, or use a different hierarchy, or to throw non-class data (e.g., fundamental types)。这是gcc编译器stl库中头文件exception中对基类exception的解释,exception类是所有其他异常类的基类。
以下源码选自vc编译器:
class exception
{
public:
exception() noexcept
: _Data()
{
}
explicit exception(char const* const _Message) noexcept
: _Data()
{
__std_exception_data _InitData = { _Message, true };
__std_exception_copy(&_InitData, &_Data);
}
exception(char const* const _Message, int) noexcept
: _Data()
{
_Data._What = _Message;
}
exception(exception const& _Other) noexcept
: _Data()
{
__std_exception_copy(&_Other._Data, &_Data);
}
exception& operator=(exception const& _Other) noexcept
{
if (this == &_Other)
{
return *this;
}
__std_exception_destroy(&_Data);
__std_exception_copy(&_Other._Data, &_Data);
return *this;
}
virtual ~exception() noexcept
{
__std_exception_destroy(&_Data);
}
virtual char const* what() const
{
return _Data._What ? _Data._What : "Unknown exception";
}
private:
__std_exception_data _Data;
};
_Data对象类型的声明:
struct __std_exception_data
{
char const* _What;
bool _DoFree;
};
_std_exception_data类型对象的拷贝和析构函数声明:
_VCRTIMP void __cdecl __std_exception_copy(
_In_ __std_exception_data const* _From,
_Out_ __std_exception_data* _To
);
_VCRTIMP void __cdecl __std_exception_destroy(
_Inout_ __std_exception_data* _Data
);
2.logic_error类:base of all logic_error exceptions
class logic_error : public exception { // base of all logic-error exceptions
public:
using _Mybase = exception;
explicit logic_error(const string& _Message) : _Mybase(_Message.c_str()) { // construct from message string
}
explicit logic_error(const char* _Message) : _Mybase(_Message) { // construct from message string
}
#if _HAS_EXCEPTIONS
#else // _HAS_EXCEPTIONS
protected:
virtual void _Doraise() const { // perform class-specific exception handling
_RAISE(*this);
}
#endif // _HAS_EXCEPTIONS
};
3.runtime_error类:base of all runtime_error exceptions
// CLASS runtime_error
class runtime_error : public exception { // base of all runtime-error exceptions
public:
using _Mybase = exception;
explicit runtime_error(const string& _Message) : _Mybase(_Message.c_str()) { // construct from message string
}
explicit runtime_error(const char* _Message) : _Mybase(_Message) { // construct from message string
}
#if _HAS_EXCEPTIONS
#else // _HAS_EXCEPTIONS
protected:
virtual void _Doraise() const { // perform class-specific exception handling
_RAISE(*this);
}
#endif // _HAS_EXCEPTIONS
};
参考资料如下:
1.《c++编程思想第二卷》
2.http://www.cplusplus.com/
3.https://en.cppreference.com/w/