异常

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

要捕获异常,我们必须将一段代码置于异常检查之下。这是通过将该代码段封装在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 << endl;
  }
  return 0;
}
An exception occurred. Exception Nr. 20

受异常处理的代码被封装在一个try块中。在这个例子中,这段代码只是抛出一个异常。

1
throw 20;

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

异常处理程序使用catch关键字声明。如您所见,它紧跟在try块的闭合花括号之后。catch的格式类似于一个常规函数,它至少有一个参数。此参数的类型非常重要,因为throw表达式传递的参数的类型会与它进行检查,只有在它们匹配的情况下,异常才会被捕获。

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

如果我们使用省略号(...)作为catch的参数,该处理程序将捕获任何类型的异常,而不管throw异常的类型是什么。如果将其放在最后,可以将其用作捕获所有未被其他处理程序捕获的异常的默认处理程序。

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-catch块嵌套在更外部的try块中。在这些情况下,我们有可能内部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";
}

异常规范


声明函数时,我们可以通过在函数声明后附加throw后缀来限制它可能直接或间接抛出的异常类型。

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

这声明了一个名为myfunction的函数,该函数接受一个类型为char的参数,并返回一个类型为float的元素。此函数可能抛出的唯一异常是类型为int的异常。如果它直接或间接抛出了不同类型的异常,则无法被常规的int-type处理程序捕获。

如果此throw说明符为空(没有类型),则表示函数不允许抛出异常。没有throw说明符的函数(常规函数)可以抛出任何类型的异常。

1
2
int myfunction (int param) throw(); // no exceptions allowed
int myfunction (int param);         // all exceptions allowed 

标准异常

C++标准库提供了一个专门用于声明要作为异常抛出的对象的基类。它被称为exception,定义在<exception>头文件中,位于std命名空间下。此类具有通常的默认和复制构造函数、运算符和析构函数,以及一个名为what的附加虚拟成员函数,该函数返回一个以null结尾的字符序列(char *),并且可以在派生类中重写以包含某种异常描述。

// 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() << endl;
  }
  return 0;
}
My exception happened.

我们放置了一个通过引用捕获异常对象的处理程序(注意类型后的&符号&),因此这也捕获了派生自exception的类,例如我们的myex对象,它是myexception.

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

exception描述
bad_alloc由 new 在分配失败时抛出。
bad_cast由 dynamic_cast 在与引用类型失败时抛出。
bad_exception当异常类型不匹配任何 catch 时抛出。
bad_typeid由 typeid 抛出。
ios_base::failure由 iostream 库中的函数抛出。

例如,如果我们使用运算符new并且无法分配内存,则会抛出类型为bad_alloc的异常。

1
2
3
4
5
6
7
8
try
{
  int * myarray= new int[1000];
}
catch (bad_alloc&)
{
  cout << "Error allocating memory." << endl;
}

建议将所有动态内存分配包含在 try 块中,该块捕获此类异常,以执行干净的操作,而不是异常的程序终止,当此类异常被抛出且未被捕获时会发生这种情况。如果你想强制一个bad_alloc异常来看它的实际效果,你可以尝试分配一个巨大的数组;在我的系统上,尝试分配 10 亿int个 字节 会抛出bad_alloc异常。

因为bad_alloc派生自标准基类exception,我们可以通过捕获exception

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;
}
 
Index
目录