异常

异常提供了一种通过将控制权转移给称为处理程序的特殊函数来响应程序中异常情况(如运行时错误)的方法。

为了捕获异常,一部分代码被置于异常检查之下。这是通过将该代码部分封装在try块中来完成的。当该块中出现异常情况时,会抛出一个异常,将控制权转移给异常处理程序。如果没有抛出异常,代码将正常继续,所有处理程序都将被忽略。

异常是通过在try块内使用throw关键字抛出的。异常处理程序使用catch关键字声明,它必须紧跟在try块之后。

// exceptions
#include <iostream>
using namespace std;

int main () {
  try
  {
    throw 20;
  }
  catch (int e)
  {
    cout << "An exception occurred. Exception Nr. " << e << '\n';
  }
  return 0;
}
An exception occurred. Exception Nr. 20

异常处理下的代码封装在try块中。在本例中,该代码只是抛出一个异常。

1
throw 20;

throw表达式接受一个参数(在本例中为整数值20),该参数作为参数传递给异常处理程序。

异常处理程序在try块的结束大括号之后立即使用catch关键字声明。catch的语法类似于带有单个参数的常规函数。此参数的类型非常重要,因为throw表达式传递的参数类型会与之进行检查,只有当它们匹配时,该异常才会被该处理程序捕获。

可以链接多个处理程序(即catch表达式);每个处理程序都具有不同的参数类型。只有参数类型与throw语句中指定的异常类型匹配的处理程序才会被执行。

如果使用省略号(...)作为catch的参数,该处理程序将捕获任何异常,无论抛出的异常类型是什么。这可以作为默认处理程序,捕获所有未被其他处理程序捕获的异常。

1
2
3
4
5
6
try {
  // code here
}
catch (int param) { cout << "int exception"; }
catch (char param) { cout << "char exception"; }
catch (...) { cout << "default exception"; }

在这种情况下,最后一个处理程序将捕获抛出的任何类型不是int也不是char的异常。

异常被处理后,程序执行将在try-catch块之后恢复,而不是在throw语句之后!。

也可以在更外部的try块中嵌套try-catch块。在这些情况下,我们有可能内部catch块将异常转发给其外部级别。这是通过不带参数的throw;表达式完成的。例如。

1
2
3
4
5
6
7
8
9
10
11
try {
  try {
      // code here
  }
  catch (int n) {
      throw;
  }
}
catch (...) {
  cout << "Exception occurred";
}

异常规范

旧代码可能包含动态异常规范。它们在C++中已被弃用,但仍然支持。动态异常规范跟在函数的声明之后,并附加一个throw说明符。例如。

1
double myfunction (char param) throw (int);

这声明了一个名为myfunction的函数,该函数接受一个char类型的参数并返回一个double类型的值。如果该函数抛出除int以外的任何类型的异常,该函数将调用std::unexpected,而不是查找处理程序或调用std::terminate

如果此throw说明符为空,不带类型,则表示任何异常都会调用std::unexpected。没有throw说明符的函数(常规函数)从不调用std::unexpected,而是遵循查找其异常处理程序的正常路径。

1
2
int myfunction (int param) throw(); // all exceptions call unexpected
int myfunction (int param);         // normal exception handling 

标准异常

C++标准库提供了一个专门用于声明要作为异常抛出的对象的基类。它称为std::exception,定义在<exception>头文件中。该类有一个名为what的虚成员函数,它返回一个以null结尾的字符序列(类型为char *),并且可以在派生类中被覆盖,以包含某种异常描述。

// using standard exceptions
#include <iostream>
#include <exception>
using namespace std;

class myexception: public exception
{
  virtual const char* what() const throw()
  {
    return "My exception happened";
  }
} myex;

int main () {
  try
  {
    throw myex;
  }
  catch (exception& e)
  {
    cout << e.what() << '\n';
  }
  return 0;
}
My exception happened.

我们放置了一个处理程序,通过引用(注意类型后的&)来捕获异常对象,因此这也捕获了派生自exception的类,比如我们类型为myexceptionmyex对象。

C++标准库组件抛出的所有异常都抛出派生自此类exception的异常。它们是:

exception描述
bad_allocnew在分配失败时抛出
bad_castdynamic_cast在动态转换失败时抛出
bad_exception由某些动态异常说明符抛出
bad_typeidtypeid抛出
bad_function_call由空function对象抛出
bad_weak_ptrshared_ptr在传递了错误的weak_ptr时抛出

同样派生自exception,头文件<exception>定义了两个通用的异常类型,可以被自定义异常继承以报告错误。

exception描述
logic_error与程序内部逻辑相关的错误
runtime_error运行时检测到的错误

需要检查标准异常的一个典型例子是内存分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// bad_alloc standard exception
#include <iostream>
#include <exception>
using namespace std;

int main () {
  try
  {
    int* myarray= new int[1000];
  }
  catch (exception& e)
  {
    cout << "Standard exception: " << e.what() << endl;
  }
  return 0;
}
 

在此示例中,异常处理程序可能捕获的异常是bad_alloc。因为bad_alloc派生自标准基类exception,所以它可以被捕获(通过引用捕获,捕获所有相关类)。
Index
目录