【C++基础】第三十三课:try语句块和异常处理

throw,try,catch,标准异常

Posted by x-jeff on November 19, 2021

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.前言

异常处理机制为程序中异常检测和异常处理这两部分的协作提供支持。在C++语言中,异常处理包括:

  1. throw表达式(throw expression),异常检测部分使用throw表达式来表示它遇到了无法处理的问题。我们说throw引发(raise)了异常。
  2. try语句块(try block),异常处理部分使用try语句块处理异常。try语句块以关键字try开始,并以一个或多个catch子句(catch clause)结束。try语句块中代码抛出的异常通常会被某个catch子句处理。因为catch子句“处理”异常,所以它们也被称作异常处理代码(exception handler)
  3. 一套异常类(exception class),用于在throw表达式和相关的catch子句之间传递异常的具体信息。

2.throw表达式

程序的异常检测部分使用throw表达式引发一个异常。throw表达式包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。throw表达式后面通常紧跟一个分号,从而构成一条表达式语句。

1
2
3
4
5
6
Sales_item item1, item2;
cin >> item1 >> item2;
//首先检查item1和item2是否表示同一种书籍
if (item1.isbn() != item2.isbn())
	throw runtime_error("Data must refer to same ISBN");
cout << item1 + item2 <<endl;

在这段代码中,如果ISBN不一样就抛出一个异常,该异常是类型runtime_error的对象。抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码。

类型runtime_error是标准库异常类型的一种,定义在stdexcept头文件中。我们必须初始化runtime_error的对象,方式是给它提供一个string对象或者一个C风格的字符串,这个字符串中有一些关于异常的辅助信息。

C++支持自定义异常类。

3.try语句块

try语句块的通用语法形式是:

1
2
3
4
5
6
7
try {
	program-statements
} catch (exception-declaration) {
	handler-statements
} catch (exception-declaration) {
	handler-statements
} // ...	

跟在try块之后的是一个或多个catch子句。catch子句包括三部分:关键字catch、括号内一个(可能未命名的)对象的声明(称作异常声明,exception declaration)以及一个块。当选中了某个catch子句处理异常之后,执行与之对应的块。catch一旦完成,程序跳转到try语句块最后一个catch子句之后的那条语句继续执行。

try语句块内声明的变量在块外部无法访问,特别是在catch子句内也无法访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
while (cin >> item1 >> item2) {
	try {
		//执行添加两个Sales_item对象的代码
		//如果添加失败,代码抛出一个runtime_error异常
	} catch (runtime_error err) {
		//提醒用户两个ISBN必须一致,询问是否重新输入
		cout << err.what() << "\nTry Again? Enter y or n" << endl;
		char c;
		cin >> c;
		if (!cin || c=='n')
			break;//跳出while循环
	}
}

catch (runtime_error err)中,err是异常类runtime_error的类对象(类似的,例如catch (int e))。每个标准库异常类都定义了名为what的成员函数,这些函数没有参数,返回值是C风格字符串(即const char*)。其中,runtime_error的what成员返回的是初始化一个具体对象时所用的string对象的副本,在本例中为”Data must refer to same ISBN”。

what函数返回的C风格字符串的内容与异常对象的类型有关。如果异常类型有一个字符串初始值,则what返回该字符串。对于其他无初始值的异常类型来说,what返回的内容由编译器决定。

catch (...)可以匹配任何类型的异常。

在复杂系统中,程序在遇到抛出异常的代码前,其执行路径可能已经经过了多个try语句块。例如,一个try语句块可能调用了包含另一个try语句块的函数,新的try语句块可能调用了包含又一个try语句块的新函数,以此类推。

寻找处理代码的过程与函数调用链刚好相反。当异常被抛出时,首先搜索抛出该异常的函数。如果没找到匹配的catch子句,终止该函数,并在调用该函数的函数中继续寻找。如果还是没有找到匹配的catch子句,这个新的函数也被终止,继续搜索调用它的函数。以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的catch子句为止。

如果最终还是没能找到任何匹配的catch子句,程序转到名为terminate的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。

对于那些没有任何try语句块定义的异常,也按照类似的方式处理:毕竟,没有try语句块也就意味着没有匹配的catch子句。如果一段程序没有try语句块且发生了异常,系统会调用terminate函数并终止当前程序的执行。

4.标准异常

C++标准库定义了一组类,用于报告标准库函数遇到的问题。这些异常类也可以在用户编写的程序中使用,它们分别定义在4个头文件中:

  • exception头文件定义了最通用的异常类exception。它只报告异常的发生,不提供任何额外信息。
  • stdexcept头文件定义了几种常用的异常类,详细信息见下表。
  • new头文件定义了bad_alloc异常类型。
  • type_info头文件定义了bad_cast异常类型。

标准库异常类只定义了几种运算,包括创建或拷贝异常类型的对象,以及为异常类型的对象赋值。

我们只能以默认初始化的方式初始化exception、bad_alloc和bad_cast对象,不允许为这些对象提供初始值。

其他异常类型的行为则恰好相反:应该使用string对象或C风格字符串初始化这些类型的对象,但是不允许使用默认初始化的方式。当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。