1 - 快速上手

程序运行过程中难免会遇到错误,C++语言提供了一个名为“异常”的特性,用来处理这些不正常但能预料到的情况。

本文将简要介绍C++异常是什么,以及它的优点等内容。

异常与错误

异常的含义

异常是这样一种机制:一段代码提醒另一段代码存在“异常”情况或错误情况,并且没有按正常的代码路径执行。发生错误的代码会 抛出(throw) 异常,处理异常的代码会 捕获(catch) 异常。当某段代码抛出异常时,程序控制立即停止逐步执行,并转向异常处理程序(exception handler)。

如图,异常的抛出和捕获就像棒球运动那样:

抛出异常捕获并处理异常

异常的优势

C程序和部分C++程序使用errno进行错误处理,在不同平台/版本下,errno值没有统一的规定,这种不一致性会引发一些麻烦。并且C++中函数的返回类型只能有一种,要想返回一个错误和一个值,就得另寻他法,例如使用std::pairstd::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高级编程