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的异常类。
用途:
- 代码复用:通过继承,PeException自动获得了
std::system_error的所有功能,尤其是那个能智能拼接字符串的what()方法,无需重复编写代码。 - 类型区分:它允许你在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.
结论:自定义异常类型是进行分类和路由错误处理的最佳实践。

浙公网安备 33010602011771号