[C++]详解异常处理(Exception Handling) 及标准库异常处理类

详解异常处理(Exception Handling) 及标准库异常处理类

异常处理,英文名为exceptional handling, 是代替日渐衰落的error code方法的新法,提供error code 所未能具体的优势。异常处理分离了接收和处理错误代码。这个功能理清了编程者的思绪,也帮助代码增强了可读性,方便了维护者的阅读和理解。 异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常情况的方法。异常处理使用 try、catch关键字来尝试可能未成功的操作,处理失败,以及在事后清理资源。

异常处理概述

异常处理是为了处理程序异常终止,并且能够显示得找到错误发生的位置。

程序终止有两种途径

  • 执行正常结束而终止。
  • 程序执行中发生错误或特殊事件而终止(异常终止)。

而异常处理机制的基本思想,就是采用结构化方法对程序的运行时错误进行显式管理!从而

  • 处理可预料的错误或特殊事件。
  • 将程序中的正常处理代码与异常处理代码显式区别开来,提高程序的可读性。

C++语言中的异常处理

基本思想:

将异常检测与异常处理分离,异常检测部分检测到异常的存在时,抛出一个异常对象给异常处理代码。通过该异常对象,独立开发的异常检测部分和异常处理部分能够就程序执行期间所出现的异常情况进行通信。

执行机制:

  1. 若有异常则通过throw操作创建一个异常对象并抛出。
  2. 将可能抛出异常的程序段放在try块之中。控制通过正常顺序执行到达try块,然后执行try子块内的保护段。
  3. 如果在保护段执行期间没有引发异常,那么跟在try子块后的catch字句就不执行。程序继续执行紧跟try块最后一个catch子句后面的语句。
  4. catch子句按其在try块后出现的顺序被检查。类型匹配的catch子句将被捕获并处理异常(或重抛出异常)
  5. 如果找不到匹配的处理代码,则自动调用terminate,默认为abort()终止程序。

throw语句

用于抛出异常,一般为

  • throw 表达式;
  • throw; // 重抛出异常

表达式的结果类型可为任意内置类型或用户自定义类型。

执行throw语句时,首先被创建被抛出对象的副本,然后将该对象副本传递到异常处理代码所在的位置,即某个catch语句的位置。

void just_throw() {
    throw MyException("inValid argument");
}

try语句

一般形式为:

try {
program-statements;
} catch(exception-declaration) {
handler-statements;
}

异常声明可以是单个类型名,单个对象声明或者为…(任意对象)

异常捕获

如果执行try语句抛出异常,则在catch子句中搜索相应的异常处理代码,异常类型的匹配规则为:

  • 被抛出异常的类型与catch子句声明的类型相同。
  • 被抛出异常的类型是catch子句声明的类型子类型。(public dereived)

注意!基类类型的处理代码一定要放在最后面,用来处理未被捕获到的异常代码。

异常处理代码的搜索

理解这个知识点对于调用带有异常抛出的函数有很大的好处。

[C++]详解异常处理(Exception Handling) 及标准库异常处理类_第1张图片

假如现在我们设计一个程序如下:

#include <iostream>

void f3(int x) {
    switch (x) {
        case 1:
            throw 3.4;
            break;
        case 2:
            throw 2.5f;
        case 3:
            throw 1;
    }
    std::cout << "end of f3" << std::endl;
}
void f2(int x) {
    try {
        f3(x);
    } catch (int) {
        std::cout << "An int exception!" << std::endl;
    } catch (float) {
        std::cout << "A float exception!" << std::endl;
    }
    std::cout << "end of f2" << std::endl;
}
void f1(int x) {
    try {
        f2(x);
    } catch (int) {
        std::cout << "An int exception!" << std::endl;
    } catch (float) {
        std::cout << "A float exception!" << std::endl;
    } catch (double) {
        std::cout << "A double exception!" << std::endl;
    }
    std::cout << "end of f1" << std::endl;
}

int main() {
    for (int i = 1; i != 4; i++) {
        f1(i);
    }

    return 0;
}

这个程序实际上反应了异常处理代码的搜索机制。

  • 如果一个函数执行时抛出了异常并且能够在该函数中处理异常,那么异常就直接被处理了。
  • 如果在该函数中无法找到相应的异常处理代码,那么就直接terminate这个函数,将异常抛给上一个函数,直到main函数。
  • 如果在main函数也无法处理该异常,就直接terminate程序。

捕获所有异常

鉴于前文提到的,如果无法找到相应的异常处理代码就会停止程序,所以我们会在main函数中使用catch(…)以此来捕获所有异常,保证程序不会异常终止。

重抛出

在函数异常处理代码中再次throw,就可以重抛出异常,并且让函数调用链更上一层的函数来处理该异常。

异常处理中的资源管理

栈展开

沿着函数调用链调用链向上搜索异常处理代码的过程。栈展开过程中,当一个函数因异常而提前终止时,编译器会自动撤销在异常发生之前创建的所有自动对象,并释放相应的内存。(此内存指的是on the stack)

存在的问题

  • 在异常发生时,如果某局部对象的构造函数尚未执行完毕,则编译器不会自动调用该对象的析构函数。
  • 如果在某个代码块中直接分配资源,且在资源释放之前发生异常,则在栈展开期间不会释放该异常。(此内存指的是on the heap)

对策

  • 针对创建对象时发生的异常,提供相应的异常处理代码,并在其中适当地撤销已有构造成员并释放所占有的资源。
  • 针对因资源的直接分配而造成的问题,定义一个资源管理类来封装资源的分配和释放。详情见文章:资源管理。

标准库异常类

[C++]详解异常处理(Exception Handling) 及标准库异常处理类_第2张图片

[C++]详解异常处理(Exception Handling) 及标准库异常处理类_第3张图片

exception源码

通过看源代码,可以基本理解这些类该如何使用,至于实现代码,由于过于复杂,此处不打算讨论。

class exception
{
public:
    exception() noexcept;
    exception(const exception&) noexcept;
    exception& operator=(const exception&) noexcept;
    virtual ~exception() noexcept;
    virtual const char* what() const noexcept;
};

定义在std命名空间的抽象类。

typeinfo头文件

class type_info
{
public:
    virtual ~type_info();

    bool operator==(const type_info& rhs) const noexcept;
    bool operator!=(const type_info& rhs) const noexcept;

    bool before(const type_info& rhs) const noexcept;
    size_t hash_code() const noexcept;
    const char* name() const noexcept;

    type_info(const type_info& rhs) = delete;
    type_info& operator=(const type_info& rhs) = delete;
};

class bad_cast
    : public exception
{
public:
    bad_cast() noexcept;
    bad_cast(const bad_cast&) noexcept;
    bad_cast& operator=(const bad_cast&) noexcept;
    virtual const char* what() const noexcept;
};

class bad_typeid
    : public exception
{
public:
    bad_typeid() noexcept;
    bad_typeid(const bad_typeid&) noexcept;
    bad_typeid& operator=(const bad_typeid&) noexcept;
    virtual const char* what() const noexcept;
};

stdexcept头文件

namespace std {
    class logic_error; // 逻辑错误
    class domain_error; // 域错误。通常用来自己定义,表明不是系统的问题。
    class invalid_argument; // 非法参数
    class length_error; // 长度错误
    class out_of_range; // 溢出
    class runtime_error; // 运行时错误
    class range_error; // 范围错误
    class overflow_error; // 上溢出
    class underflow_error; // 下溢出

for each class xxx_error:

class xxx_error : public exception // at least indirectly
{
public:
    explicit xxx_error(const string& what_arg);
    explicit xxx_error(const char*   what_arg);

    virtual const char* what() const noexcept // returns what_arg
};

}  // std

总结

异常处理只是当程序发生错误时,可以很快地定为错误位置,提高程序的健壮性,但我们也需要注意到,使用异常处理的开销比较大,所以如果能够使用简单的条件语句,就不要使用异常处理。

你可能感兴趣的:([C++]详解异常处理(Exception Handling) 及标准库异常处理类)