1 - 快速上手
程序运行过程中难免会遇到错误,C++语言提供了一个名为“异常”的特性,用来处理这些不正常但能预料到的情况。
本文将简要介绍C++异常是什么,以及它的优点等内容。
异常与错误
异常的含义
异常是这样一种机制:一段代码提醒另一段代码存在“异常”情况或错误情况,并且没有按正常的代码路径执行。发生错误的代码会 抛出(throw) 异常,处理异常的代码会 捕获(catch) 异常。当某段代码抛出异常时,程序控制立即停止逐步执行,并转向异常处理程序(exception handler)。
如图,异常的抛出和捕获就像棒球运动那样:
异常的优势
C程序和部分C++程序使用errno
进行错误处理,在不同平台/版本下,errno
值没有统一的规定,这种不一致性会引发一些麻烦。并且C++中函数的返回类型只能有一种,要想返回一个错误和一个值,就得另寻他法,例如使用std::pair
或std::tuple
;使用自定义数据结构等方法,很麻烦。
异常提供了方便、一致、安全的错误处理机制。相对于C和C++中的专门方法,异常具有许多优点:
- 异常不能被忽略,而调用者可能会把返回的其他数据结构忽略(例如
tuple
啥的) - 异常包含足够的错误信息,并且它还能用来传递其他信息。
- 异常处理可跳过调用栈的层次,这样就不用每层都处理返回的错误信息了。
使用异常
抛出异常
要想抛出异常,可以使用throw
关键字。这里以除以0为例,如果除数是0,则抛出std::invalid_argument
类型的异常,它定义在<stdexcept>
中:
double safeDiv(double num, double den)
{
if (den == 0)
{
throw invalid_argument("Divide by zero");
}
return num / den;
}
实际上可以抛出任何类型的异常,可以是上边的异常对象,也可以是简单的int
值,C风格字符串等。但一般应抛出基于std::exception
类型的异常。
捕获异常
可使用try/catch
结构来捕获抛出的异常,并进行处理:
try
{
// 可能会发生异常的代码段
}
catch (exception-type1 name)
{
// 处理发生种类1异常
}
catch (exception-type2 name)
{
// 处理发生种类2异常
}
// 剩余的代码...
如果没有抛出异常,执行完try后会执行剩余的代码;如果抛出异常,则会立即执行对应catch块的代码,如果catch块代码没有执行控制转移操作(结束程序,从函数中返回等),则会执行完catch代码后执行剩余的代码。
对于上例异常的捕获,可以这样写:
try
{
cout << safeDiv(5, 2) << endl;
cout << safeDiv(10, 0) << endl;
}
catch (const invalid_argument& e)
{
cerr << "Caught exception: " << e.what() << endl;
}
其中,.what()
可以返回该异常的描述信息。
此外,这样写可以捕获所有类型的异常:
catch (...)
{
// ......
}
如果想要重新抛出异常,可以这样写:
catch (const exception& e)
{
throw;
}
注意,这里不能用throw e
,因为那样会截断异常对象。
参考资料
- 飘零的落花 - 现代C++详解
- C++20高级编程