870920 Menu

SwingCoder之C++备忘录·17

异常处理

先看一个最简单的示例:
#include <iostream>
#include <stdexcept>
using namespace std;

// 此函数的返回值为1参除以2参的结果
double divi(double chushu, double beichushu)
{
// 被除数为0则抛出异常,该异常为exception类的栈对象(临时对象)
if (beichushu == 0)
{
throw exception(); // 抛出异常后,代码将不再继续执行,而是从此处直接返回
}
return chushu / beichushu;
}
int main() // 主调函数中使用try块和catch块
{
try // 有可能抛出异常的函数,写在try块中
{
cout << divi(4.0, 2.0) << endl; // 正常调用,此时未抛出异常
cout << divi(4.0, 0) << endl; // 此处将抛出异常(被除数为0)
cout << divi(8.0, 2.0) << endl; // 抛出异常后,不会执行此句
}
catch (const exception&) // 紧接着try块,在此捕获exception类的对象
{
cout << “捕获异常!” << endl;
}
// …更多catch块
// 捕获并处理异常后,程序从此处继续执行
system(“pause”);
return 0;
}
 异常是为C++增加安全性和另外一种错误处理技术的语言特性,它提供了与代码的正常流程完全不同的处理方式,可以将当前执行的流程强制跳转到上一层次(调用方)中。
 使用异常可写出更加健壮并具有容错能力的程序,使程序在异常情况下能够继续运行或得体地终止。
 异常三步走:try中的函数若throw,try后的catch来捕获。
 异常处理是C++语言内置的机制与功能。异常由正常的控制流程之外的代码来单独处理,即异常可直接跳出当前的执行环境(当前函数),将程序的流程转到另一个环境中(另一个函数或另一个类的成员函数)。
 异常机制可以区分并隔离错误处理代码与程序的功能代码,使编码更简洁和灵活,增加程序的Robust(鲁棒性,即:即使程序有一些异样,也可正常运行)。
 能不用异常,尽量不要用异常。假如有足够的信息和简便的方式来判断并处理一个可能潜在的错误,那么这个错误就不能按异常来处理。另外,避免在构造函数与析构函数中使用异常。
 如果构造函数中抛出异常(尽量不要这么做),则该类的析构函数最好为空。
 如果当前执行环境无法捕捉或确定错误类型,那么可以利用异常机制将其抛到更上层的执行环境中。
 上层环境中处理异常的原则:消除异常,解决问题,恢复原状,继续执行,一如未出现异常。
 抛出异常:throw Exception (“This is a exception.”); 此处Exception是一个自定义的类,该类的构造参数为一个字符串,用来给出异常的描述。这条语句等于是直接抛出一个Exception类的对象。
 某个函数如有可能抛出异常,则最好在其声明语句的最后添加:throw(处理异常的类1, 处理异常的类2),此为“异常规格说明”,用来提醒调用者:该函数可能会抛出这两种类型的异常。比如:
void func(int value) throw (ExceptionClass1, ExceptionClass2);
指定异常规格后,该函数中只能抛出规格中所写明的异常类对象,不能抛出其他类型的异常对象。
 throw(…)这种写法意味着该函数有可能抛出任何类型的异常,即未指定具体的异常规格。
 无异常规格的throw()意味着该函数不抛出任何异常。如果函数中使用了throw 异常类的对象,则编译器报错。
 函数声明最后的throw(有或无异常规格)这种做法已被C++ 11所摒弃。即:无需也不要再使用函数声明最后的throw(有或无异常规格)。一个例外是:可代之以noexcept关键字,标明此函数不会抛出任何异常。
 注意:函数声明最后的noexcept关键字和运算符noexcept()性质不同,带小括号的运算符noexcept()用于判断括号内的操作数是否会引发异常。
 如果第三方代码的函数声明中没有给出throw(异常规格),则必须查看该函数的源代码,看看是否抛出了异常,抛出了什么类型的异常。
 捕获与处理异常在上层环境(另一个函数或另一个类的成员函数中)中使用try…catch…结构。
 将有可能抛出异常的函数写在try块中:
try
{
// 有可能抛出异常的代码(函数)…
}
 在catch()语句块中捕获并处理异常。catch语句块需紧接着try语句块(中间不能有任何其他语句):
catch(ExceptionClass1& e1) // catch()非常类似一个函数,其参数为异常类的栈对象
{
e1.processException(); // 用自定义的异常类来处理异常
}
 catch (…) 表示捕获所有类型的异常。
 catch语句块中捕获到异常后,可将该异常再次抛出:
catch (ExceptionClass1& e1)
{ // …
throw; // 再次抛出。注意:直接抛出,而不是throw e1;
}
 函数中要抛出值对象,而不要抛出指针(堆对象),catch中捕获该对象的引用(通常是const引用)。
 函数中可直接抛出char*字符数组(字串),catch (char* c)捕获之:
void func ()
{
// …
throw “exception…”;
}
try { func(); }
catch (char* s) { // … }

C++标准库中内置了一大批异常类,有可能抛出异常的函数中可直接抛出这些类的对象,函数声明中的“异常规格说明”写法为:void func() throw (异常类的类名1, 异常类的类名2);
内置的异常类位于标准库头文件<exception>和<stdexcept>中,使用时需#include之。这些异常类全部继承自基类exception,继承结构与各自的作用如下:
exception C++异常总基类
|—- logic_error 报告程序的逻辑错误,可在程序执行前进行检测
| |—- domain_error 违反了前置条件
| |—- invalid_argument 函数的参数无效
| |—- length_error 对象的长度超过size_t的最大值
| |—- out_of_range 参数越界
| |—- bad_cast 无效的dynamic_cast强转
| |—- bad_typeid 运算符typeid(*p)的操作对象p为空指针
|—- runtime_error 报告程序运行时的错误,只在运行时才能检测到
|—- range_error 违反了后置条件
|—- overflow_error 算术溢出
|—- bad_alloc 内存分配错误
表 1 8 C++内置的异常类