C++异常处理

异常处理

noexcept

C++11后将异常的声明简化为以下两种情况:函数可能抛出任何异常;函数不能抛出任何异常.

使用noexcept(代表绝对不会抛出异常)修饰过的函数如果抛出异常,编译器会立即终止程序运行。未声明代表可能,需要用try,catch,throw来解决.

noexcept还能使用表达式来判断.

在明确不会抛出异常,比如:简单的数值计算函数,析构(析构已经隐式noexcept了),移动操作相关函数,频繁调用或者性能敏感的函数,接口(给调用方了解情况,这是必要的),最好使用.

如果有个函数调用其他函数,并且不知道调用的可否,那就是异常中立,就不要使用.

try…catch块

传统的异常处理是有错误码来实现的,通过if来层层检查.不过这有个问题,会有很多判断,较难看(因人而异).

故而C++发明了try…catch块,它的用法是:

int main(...) {
    try {
        // ---------------------------------
        // 所有可能抛出异常的“正常”业务逻辑都在这里
        // ---------------------------------
    }
    catch (const std::exception& e) {
        // ---------------------------------
        // 如果 try 块中的任何地方抛出了异常,
        // 程序会立即跳转到这里来处理
        // ---------------------------------
        std::cerr << "Error: " << e.what() << '\n';
        return 1;
    }
    return 0;
}
  • try 块:包裹了程序的主要逻辑。它像一个“安全网”,表示“请尝试执行这里的代码,如果出了问题,我知道如何处理”。
  • catch 块:这是异常的“捕手”。 catch (const std::exception& e) 表示它能捕获所有继承自 std::exception 的标准异常类型。当异常被抛出时,程序会立即停止在 try 块中的执行,跳转到类型匹配的 catch 块。
  • e.what():std::exception 类及其所有子类都有一个名为 what() 的虚函数,它返回一个描述错误的C风格字符串(const char*)。这是获取异常信息的基本方式。

throw和栈回退

可以用throw抛出一个异常(或者try块内部出异常时也会自动捕获),这时候会跟随调用栈一路回去,然后跑到catch块中,保证内存安全.

1. 错误码抛出

在Windows API中,可以用GetLastError()来获取当前线程上一个API调用失败的错误码:

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

void demonstrate_getlasterror() {
    // 尝试打开一个不存在的文件,这必然会失败
    HANDLE hFile = CreateFileA(
        "C:\\this_file_does_not_exist.tmp", 
        GENERIC_READ, 
        0, 
        NULL, 
        OPEN_EXISTING, 
        FILE_ATTRIBUTE_NORMAL, 
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        // CreateFileA 失败了。我们必须立即调用 GetLastError()
        DWORD errorCode = GetLastError();
        
        // 此刻,errorCode 的值通常是 2
        std::cout << "CreateFileA failed. The raw error code is: " << errorCode << std::endl; 
        
        // 如果我们现在调用另一个API,比如Sleep,错误码可能会被覆盖
        // Sleep(100);
        // DWORD potentiallyChangedErrorCode = GetLastError(); // 结果可能不再是 2
    }
}
```**输出**:
`CreateFileA failed. The raw error code is: 2`

**结论**:`GetLastError()`返回一个`DWORD`(一个整数)。这个数字`2`本身没有程序化的可读性,需要被解释。

在Linux API中,使用一个叫做errno的全局变量来实现的,每一个线程都有自己单独的errno

在标准的C函数用 strerror(),它接受一个int类型的错误码,返回一个描述该错误的字符串 (const char*)。就可以用它来解释errno,当然这是C语言的实现.

2. std::system_category()

功能:这是一个C++标准库函数,返回一个const std::error_category&对象。这个对象是一个全局的“翻译器”,专门用于解释特定于操作系统的错误码

用途:为原始的、无意义的数字错误码提供一个“上下文”或“解释机制”。它知道如何将数字2翻译成字符串"The system cannot find the file specified."。

代码示例

#include <system_error>
#include <iostream>

void demonstrate_category() {
    // 获取系统错误码的“翻译器”
    const std::error_category& category = std::system_category();
    
    // 使用这个翻译器来解释数字 2 和 5
    int errorCode_FileNotFound = 2;
    int errorCode_AccessDenied = 5;

    std::cout << "Code " << errorCode_FileNotFound << " means: " 
              << category.message(errorCode_FileNotFound) << std::endl;

    std::cout << "Code " << errorCode_AccessDenied << " means: " 
              << category.message(errorCode_AccessDenied) << std::endl;
}

在Windows上的输出

Code 2 means: The system cannot find the file specified.
Code 5 means: Access is denied.

结论:std::system_category()提供了一个标准化的接口(.message()方法)来将平台相关的错误数字翻译成人类可读的字符串。

3. std::error_code

功能:这是一个C++标准库类,用于封装一个错误码的数值和它的解释类别(std::error_category)

用途:将一个原始的错误数字(如2)和它的翻译官(std::system_category()的返回结果)绑定在一个对象中,使其成为一个自包含、可移植的错误信息单元。

代码示例

#include <system_error>
#include <iostream>
#include <windows.h> // For GetLastError

void demonstrate_error_code() {
    // 模拟API失败
    SetLastError(2); // 手动设置当前线程的错误码为 2
    DWORD rawErrorCode = GetLastError();

    // 创建一个 std::error_code 对象
    std::error_code ec(rawErrorCode, std::system_category());

    // 现在 ec 对象同时包含了数值和解释方法
    std::cout << "ec.value() returns the number: " << ec.value() << std::endl;
    std::cout << "ec.message() returns the text: " << ec.message() << std::endl;
    std::cout << "ec.category().name() returns the translator's name: " << ec.category().name() << std::endl;
}

输出

ec.value() returns the number: 2
ec.message() returns the text: The system cannot find the file specified.
ec.category().name() returns the translator's name: system

结论:std::error_code是一个比int更强大的错误容器,因为它自身就携带了解释自己的能力。

4. std::system_error

功能:这是一个C++标准库异常类,继承自std::runtime_error。它被设计用来抛出包含了std::error_code的异常。

用途:将一个底层的、具体的std::error_code与一个高层的、描述程序意图的字符串消息组合在一起,形成一个信息量极大的异常对象。

代码示例

#include <system_error>
#include <iostream>
#include <string>

void demonstrate_system_error() {
    // 1. 创建一个代表底层错误的 error_code
    std::error_code ec(2, std::system_category());

    // 2. 提供一个高层上下文消息
    std::string context_message = "Error while attempting to load configuration from file";

    // 3. 用这两部分信息构造一个 system_error 异常对象
    std::system_error se(ec, context_message);

    // 4. 检查这个异常对象包含的信息
    std::cout << "The full error message is: " << se.what() << std::endl;
    std::cout << "The original error code is: " << se.code().value() << std::endl;
}

输出

The full error message is: Error while attempting to load configuration from file: The system cannot find the file specified.
The original error code is: 2

结论:std::system_error是用于throw的理想对象,因为它将“做了什么”(字符串)和“具体为什么失败”(error_code)结合在了一起。

5. 自定义异常(下例为我让ai帮我写的一个自定义异常,关于PE文件的读取)

功能:一个您自己定义的、继承自std::system_error的异常类。

用途

  1. 代码复用:通过继承,PeException自动获得了std::system_error的所有功能,尤其是那个能智能拼接字符串的what()方法,无需重复编写代码。
  2. 类型区分:它允许你在catch块中精确地捕获只与PE文件处理相关的错误,从而实现更精细的错误处理逻辑。

代码示例

#include <system_error>
#include <iostream>
#include <string>

// 您的 PeException 类定义
class PeException : public std::system_error {
public:
    using std::system_error::system_error; // 继承构造函数
};

void process_data() {
    // 模拟一个非PE相关的系统错误
    throw std::system_error(std::error_code(5, std::system_category()), "Failed to allocate memory");
}

void process_pe_file() {
    // 模拟一个PE文件处理相关的错误
    throw PeException(std::error_code(2, std::system_category()), "Failed to open PE file");
}

void demonstrate_pe_exception() {
    try {
        process_pe_file(); // 试着调用这个
        // process_data(); // 或者试着调用这个,会进入不同的 catch 块
    }
    catch (const PeException& e) {
        // 这个块只会捕获 PeException 类型的异常
        std::cerr << "[PE Handler] Caught a PE-specific error: " << e.what() << std::endl;
    }
    catch (const std::system_error& e) {
        // 这个块会捕获其他所有 system_error (但不会是 PeException,因为它已被上面捕获)
        std::cerr << "[Generic System Handler] Caught a generic system error: " << e.what() << std::endl;
    }
}

输出

[PE Handler] Caught a PE-specific error: Failed to open PE file: The system cannot find the file specified.

结论:自定义异常类型是进行分类和路由错误处理的最佳实践。

posted @ 2025-08-30 13:50  T0fV404  阅读(8)  评论(0)    收藏  举报