异常处理机制

程序运行时常会碰到一些异常情况,例如:

  • 做除法的时候除数为 0;
  • 用户输入年龄时输入了一个负数;
  • 用 new 运算符动态分配空间时,空间不够导致无法分配;
  • 访问数组元素时,下标越界;
  • 打开文件读取时,文件不存在。

这些异常情况,如果不能发现并加以处理,很可能会导致程序崩溃。

所谓“处理”,可以是给出错误提示信息,然后让程序沿一条不会出错的路径继续执行;也可能是不得不结束程序,但在结束前做一些必要的工作,如将内存中的数据写入文件、关闭打开的文件、释放动态分配的内存空间等。

一发现异常情况就立即处理未必妥当,因为在一个函数执行过程中发生的异常,在有的情况下由该函数的调用者决定如何处理更加合适。尤其像库函数这类提供给程序员调用,用以完成与具体应用无关的通用功能的函数,执行过程中贸然对异常进行处理,未必符合调用它的程序的需要。

此外,将异常分散在各处进行处理不利于代码的维护,尤其是对于在不同地方发生的同一种异常,都要编写相同的处理代码也是一种不必要的重复和冗余。如果能在发生各种异常时让程序都执行到同一个地方,这个地方能够对异常进行集中处理,则程序就会更容易编写、维护。

鉴于上述原因,C++ 引入了异常处理机制。其基本思想是:

  1. 函数 A 在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给 A 的调用者(假定为函数 B),然后 A 立即中止;
  2. 在这种情况下,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。
  3. 如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。如果main函数也不处理异常,那么程序就会立即中止。

异常抛出与捕获

要处理异常,就需要捕获异常。C++ 通过 try...catch 语句实现对异常的捕获和处理。

try {
    语句组
}
catch(异常类型) {
    异常处理代码
}
...
catch(异常类型) {
    异常处理代码
}
// catch 可以有多个,但至少要有一个

try…catch 语句的执行过程是:

  • 如果在 try 块执行的过程中没有异常拋出,那么执行完后就略过所有 catch 块中的语句,继续向下执行;
  • 如果 try 块执行的过程中中拋出了异常,那么拋出异常后立即跳转到第一个和拋出的异常类型相匹配的 catch 块中执行,称作异常被该 catch 块“捕获”,在其中执行完异常处理程序后,再跳转到最后一个 catch 块后面继续向下执行。因为异常而未执行的语句组将不再执行。

除了C++运行时等底层自身抛出异常外,也可以通过throw主动抛出异常。

throw  表达式;	// 表达式的值可以是基本类型,也可以是类

异常类型

C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的。常用的几个异常类如下所示。

C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。这些异常类还都有名为 what() 的成员函数,返回字符串形式的异常描述信息。使用这些异常类需要包含头文件 <exception>

代码示例:抛出并捕获异常

捕获通用异常

#include <exception>

try 
{
    // do something
}
catch (std::exception & e) 
{
    std::cerr <<"ERROR: "<< e.what() << std::endl;
}

捕获特定异常 *

#include <exception>

try 
{
    // do something
}
catch (std::bad_cast & e) 
{
    std::cerr <<"ERROR: "<< e.what() << std::endl;
}

捕获throw异常

#include <string>

try 
{
    if(判断语句) throw std::string("Stack is empty!"); 
    if(判断语句) throw -1;
    // do something
}
catch (std::string & e) 
{
    std::cerr <<"ERROR: "<< e << std::endl;
}
catch (int & e) 
{
    std::cerr <<"ERROR CODE: "<< e << std::endl;
}

注意:这种throw抛出的是基本类型,不能使用 std::exception 类来捕获。

捕获任何异常 *

try 
{
    // do something
}
catch (...) 
{
    std::cerr << "ERROR!" << std::endl;
}

捕获自定义异常 *

#include <iostream>
#include <exception>

using namespace std;
 
struct MyException : public exception
{
  const char * what () const throw ()
  {
    return "C++ Exception";
  }
};
 
int main()
{
  try
  {
    throw MyException();
  }
  catch(MyException& e)
  {
    std::cout << "MyException caught" << std::endl;
    std::cout << e.what() << std::endl;
  }
  catch(std::exception& e)
  {
    //其他异常处理
  }
}