一、异常处理的基本概念

在编程的世界里,异常处理是一个很重要的事儿。简单来说,异常就是程序在运行过程中出现的意外情况,比如访问了不存在的内存地址、除零错误等等。如果不处理这些异常,程序可能就会崩溃,给用户带来不好的体验。

C++ 里有两种常见的异常处理方式,一种是结构化异常处理(SEH),另一种是标准异常。SEH 是 Windows 系统特有的,它可以捕获系统级的异常,像访问违规、除零错误等。而标准异常是 C++ 语言本身提供的,它基于类和对象,通过抛出和捕获异常对象来处理异常。

二、结构化异常处理(SEH)

2.1 SEH 的基本原理

SEH 的核心思想是通过设置异常处理函数,当程序发生异常时,系统会自动调用这些处理函数。在 Windows 系统中,SEH 主要有两种类型:__try__except__try__finally

__try__except 用于捕获和处理异常,__try 块里放可能会出现异常的代码,__except 块用于处理捕获到的异常。而 __try__finally 则保证无论是否发生异常,__finally 块里的代码都会执行。

2.2 SEH 示例

下面是一个简单的 SEH 示例,使用 C++ 技术栈:

#include <iostream>
#include <windows.h>

// 自定义异常处理函数
LONG WINAPI MyExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) {
    // 输出异常信息
    std::cout << "Exception code: " << ExceptionInfo->ExceptionRecord->ExceptionCode << std::endl;
    return EXCEPTION_EXECUTE_HANDLER; // 表示处理该异常
}

int main() {
    __try {
        // 这里会触发除零错误
        int result = 1 / 0; 
        std::cout << "Result: " << result << std::endl;
    }
    __except (MyExceptionFilter(GetExceptionInformation())) {
        std::cout << "Exception caught by SEH!" << std::endl;
    }
    return 0;
}

在这个示例中,__try 块里的代码会触发除零错误,当异常发生时,系统会调用 MyExceptionFilter 函数,该函数会输出异常代码,并返回 EXCEPTION_EXECUTE_HANDLER 表示处理该异常。最后,__except 块会输出异常被捕获的信息。

三、标准异常

3.1 标准异常的基本原理

C++ 的标准异常是基于类层次结构的,所有标准异常类都继承自 std::exception 类。当程序出现异常时,可以抛出一个异常对象,然后在合适的地方捕获这个对象进行处理。

3.2 标准异常示例

下面是一个简单的标准异常示例,同样使用 C++ 技术栈:

#include <iostream>
#include <stdexcept>

// 自定义函数,可能会抛出异常
int divide(int a, int b) {
    if (b == 0) {
        // 抛出除零异常
        throw std::runtime_error("Division by zero!"); 
    }
    return a / b;
}

int main() {
    try {
        int result = divide(10, 0);
        std::cout << "Result: " << result << std::endl;
    }
    catch (const std::exception& e) {
        // 捕获并输出异常信息
        std::cout << "Exception caught: " << e.what() << std::endl; 
    }
    return 0;
}

在这个示例中,divide 函数会检查除数是否为零,如果为零则抛出 std::runtime_error 异常。在 main 函数中,使用 try 块调用 divide 函数,catch 块捕获并处理抛出的异常。

四、SEH 与标准异常的结合使用

4.1 结合的必要性

有时候,我们既需要处理系统级的异常(SEH 擅长的),又需要处理程序逻辑上的异常(标准异常擅长的)。这时候,就需要将 SEH 和标准异常结合起来使用。

4.2 结合示例

下面是一个 SEH 与标准异常结合使用的示例,使用 C++ 技术栈:

#include <iostream>
#include <windows.h>
#include <stdexcept>

// 自定义 SEH 异常处理函数
LONG WINAPI MyExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) {
    switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
    case EXCEPTION_ACCESS_VIOLATION:
        // 抛出标准异常
        throw std::runtime_error("Access violation!"); 
    default:
        return EXCEPTION_CONTINUE_SEARCH; // 继续搜索其他处理程序
    }
}

int main() {
    __try {
        try {
            // 这里会触发访问违规异常
            int* ptr = nullptr;
            *ptr = 10; 
        }
        catch (const std::exception& e) {
            std::cout << "Standard exception caught: " << e.what() << std::endl;
        }
    }
    __except (MyExceptionFilter(GetExceptionInformation())) {
        std::cout << "SEH exception caught and converted to standard exception!" << std::endl;
    }
    return 0;
}

在这个示例中,__try 块里的代码会触发访问违规异常,SEH 异常处理函数 MyExceptionFilter 会捕获这个异常,并将其转换为标准异常 std::runtime_error 抛出。然后,try 块里的 catch 块会捕获并处理这个标准异常。

五、应用场景

5.1 系统级异常处理

当程序需要处理系统级的异常,如访问违规、除零错误等,SEH 可以很好地发挥作用。例如,在开发 Windows 系统下的底层程序时,SEH 可以帮助我们捕获和处理这些异常,避免程序崩溃。

5.2 程序逻辑异常处理

标准异常更适合处理程序逻辑上的异常,如输入验证失败、资源分配失败等。例如,在一个文件处理程序中,如果文件打开失败,可以抛出一个标准异常来通知调用者。

5.3 混合异常处理

在一些复杂的程序中,可能既会遇到系统级的异常,又会遇到程序逻辑上的异常。这时候,将 SEH 和标准异常结合使用,可以更全面地处理各种异常情况。

六、技术优缺点

6.1 SEH 的优缺点

优点

  • 可以捕获系统级的异常,对系统底层的异常处理能力强。
  • 不需要修改代码结构,只需要设置异常处理函数即可。

缺点

  • 是 Windows 系统特有的,不具有跨平台性。
  • 异常处理函数的编写比较复杂,需要对系统底层有一定的了解。

6.2 标准异常的优缺点

优点

  • 具有良好的跨平台性,适用于各种操作系统。
  • 基于类和对象,异常处理更加灵活,可以自定义异常类。

缺点

  • 对于系统级的异常处理能力较弱,无法直接捕获系统底层的异常。

6.3 结合使用的优缺点

优点

  • 可以同时处理系统级和程序逻辑上的异常,提高程序的健壮性。
  • 充分发挥了 SEH 和标准异常的优势。

缺点

  • 增加了代码的复杂度,需要开发者对两种异常处理方式都有一定的了解。

七、注意事项

7.1 异常处理顺序

在结合使用 SEH 和标准异常时,要注意异常处理的顺序。一般来说,先使用 SEH 捕获系统级的异常,然后将其转换为标准异常抛出,再使用标准异常的 try-catch 块进行处理。

7.2 资源管理

在异常处理过程中,要注意资源的管理。特别是在 __finally 块中,要确保释放所有已分配的资源,避免内存泄漏。

7.3 异常安全性

在编写异常处理代码时,要考虑异常安全性。例如,在异常发生时,要确保对象的状态不会被破坏,避免出现不一致的情况。

八、文章总结

通过本文的介绍,我们了解了 C++ 中结构化异常处理(SEH)和标准异常的基本原理,以及如何将它们结合使用。SEH 适用于处理系统级的异常,标准异常适用于处理程序逻辑上的异常,将它们结合起来可以更全面地处理各种异常情况。

在实际开发中,我们可以根据具体的应用场景选择合适的异常处理方式。同时,要注意异常处理的顺序、资源管理和异常安全性等问题,以提高程序的健壮性和可靠性。