当前位置: 首页 > news >正文

error_code

1. 为什么需要 error_category

在 C++ 开发中,我们经常遇到从底层(操作系统、网络库、第三方库)返回的 int 类型的错误码。

核心问题:数字是有歧义的。

  • 错误码 1Linux 系统 中可能表示 "Operation not permitted" (EPERM)。
  • 错误码 1HTTP 协议 中可能完全不是这个意思。
  • 错误码 1你的自定义库 中可能表示“初始化失败”。

如果只给你一个数字 1,你无法知道它到底是什么错误。std::error_category 的作用就是给这个数字赋予“上下文(Context)”

2. 它是如何工作的?

C++ 使用 std::error_code 对象来表示一个具体的错误,它内部包含两部分:

  1. Value (int): 错误的数值(例如 1, 404, 500)。
  2. Category (const std::error_category\*): 一个指向分类对象的指针,标明这个数字属于哪个领域。

$$\text{std::error_code} = \text{整数值} + \text{错误类别指针}$$

判断两个错误是否相等时,不仅比较数值,还要比较类别。

3. 标准库自带的类别

C++ 标准库预定义了几个常见的 error_category(它们都是单例):

  1. std::generic_category(): 对应标准的 POSIX 错误码(如 errno)。
  2. std::system_category(): 对应操作系统的底层错误(在 Windows 上可能是 Win32 API 错误,在 Linux 上通常和 generic 一样)。
  3. std::iostream_category(): 用于 IO 流的错误。

4. 代码示例:同样的数字,不同的含义

这个例子展示了即使错误数值都是 1,因为类别不同,它们被视为不同的错误。

#include <iostream>
#include <system_error>
#include <string>int main() {// 创建两个错误码,数值都是 1,但类别不同std::error_code ec_system(1, std::system_category());std::error_code ec_generic(1, std::generic_category());std::cout << "错误 1 (System): " << ec_system.message() << std::endl;std::cout << "错误 1 (Generic): " << ec_generic.message() << std::endl;// 比较它们if (ec_system == ec_generic) {std::cout << "它们是同一个错误" << std::endl;} else {std::cout << "它们是不同的错误 (因为 category 不同)" << std::endl;}// 获取类别名称std::cout << "Category Name: " << ec_system.category().name() << std::endl;return 0;
}

5. 进阶:如何自定义 error_category

这是 error_category 最强大的地方。如果您在写一个库(比如叫 MyLib),您不应该直接返回 int,也不应该借用系统的错误码。您应该定义自己的类别。

实现步骤如下:

  1. 继承 std::error_category
  2. 实现 name() 方法(返回类别名称)。
  3. 实现 message(int) 方法(根据错误码返回对应的字符串解释)。
  4. 定义一个单例函数来获取这个类别。
// 简化的自定义类别示例
class MyLibCategory : public std::error_category {
public:const char* name() const noexcept override {return "MyLib";}std::string message(int ev) const override {switch (ev) {case 1: return "MyLib: 初始化失败";case 2: return "MyLib: 网络连接断开";default: return "MyLib: 未知错误";}}
};// 获取单例
const std::error_category& my_lib_category() {static MyLibCategory instance;return instance;
}int main() {// 创建一个属于我们自己库的错误std::error_code my_err(2, my_lib_category());std::cout << "错误信息: " << my_err.message() << std::endl;// 输出: 错误信息: MyLib: 网络连接断开return 0;
}

简单来说:

  1. error_category 负责定义错误的身份(是系统错误?还是网络库错误?)。
  2. std::system_error 是一个容器,它把 error_category + 错误码 包装成一个异常对象。
  3. exception_ptr 负责把这个异常对象在线程之间搬运

6.联合使用的工作流

这种模式通常用于:底层(基于错误码的)API 在子线程报错,需要将具体的错误码和类别完整地传回主线程处理。

流程步骤:

  1. 产生错误:底层 API 返回一个 int 错误码。
  2. 打包:使用 std::error_code (包含 interror_category) 创建一个 std::system_error 异常并抛出。
  3. 捕获与保存:在子线程捕获该异常,用 std::current_exception 存入 exception_ptr
  4. 传递:将指针传给主线程(通过 std::promise 或共享变量)。
  5. 解包与处理:主线程 rethrow,捕获 std::system_error,然后从中取出原始的 error_category 进行精确判断。

代码示例

这个例子模拟了一个子线程调用底层 OS API(返回错误码),然后主线程精准识别该错误的场景。

#include <iostream>
#include <thread>
#include <future>
#include <system_error>
#include <fstream>// 模拟一个底层的、基于错误码的函数
// 比如它返回 errno 风格的错误码
int lowLevelOsOperation() {// 模拟错误:2 通常代表 ENOENT (No such file or directory)return 2; 
}void workerThread(std::promise<void> prom) {try {int err = lowLevelOsOperation();if (err != 0) {// 【关键点 1】将 错误码 + 类别 打包成 std::system_error 抛出// 这里我们明确指明这是 generic_category (POSIX 标准错误)throw std::system_error(std::error_code(err, std::generic_category()), "读取底层文件失败");}prom.set_value();} catch (...) {// 【关键点 2】捕获异常并转为 exception_ptr (存入 promise)prom.set_exception(std::current_exception());}
}int main() {std::promise<void> prom;std::future<void> fut = prom.get_future();std::thread t(workerThread, std::move(prom));try {// 等待结果,如果有异常会在这里重新抛出fut.get();} catch (const std::system_error& e) { // 【关键点 3】专门捕获 system_error// 【关键点 4】拆包:获取原始的错误码和类别const std::error_code& ec = e.code();std::cout << "--- 主线程捕获系统错误 ---" << std::endl;std::cout << "错误描述: " << e.what() << std::endl;std::cout << "错误数值: " << ec.value() << std::endl;std::cout << "错误类别: " << ec.category().name() << std::endl;// 精确判断:不仅判断数值,还判断类别if (ec.category() == std::generic_category() && ec.value() == 2) {std::cout << ">> 判定原因:找不到指定的文件。" << std::endl;}} catch (const std::exception& e) {std::cout << "捕获到普通异常: " << e.what() << std::endl;}t.join();return 0;
}

为什么要这么麻烦?(优势)

如果只用 std::runtime_error("error 2") 传递字符串,主线程就得解析字符串来猜错误原因,非常脆弱。

联合使用 exception_ptr + system_error (含 error_category) 的好处是:

  1. 类型安全:主线程可以拿到原始的 int 错误码,而不是文本。
  2. 上下文明确:主线程知道这个 2generic_category 里的 2(文件不存在),而不是 iostream_category 里的 2
  3. 无损传递:异常从子线程到主线程,就像在同一个线程里抛出一样,保留了所有的元数据。

这种模式是 C++ 开发高性能服务器(如基于 Asio 网络库)时的标准错误处理范式。

http://www.jsqmd.com/news/115930/

相关文章:

  • 虚拟化初步了解
  • Miloco 深度打通 Home Assistant,实现设备级精准控制
  • 好用的大型牛场水滴粉碎机技术强的
  • set_value
  • 日记1217
  • function的类型擦除
  • function bind
  • 日记12,19
  • Item10--令赋值操作符返回一个
  • Item9--绝不在构造和析构过程中调用虚函数
  • python django flask考研互助交流平台_c62p51fu--论文
  • 日记12.18
  • 离散化遍历
  • Ubuntu上使用VScode创建Maven项目
  • 线程(2)
  • 大规模语言模型的抽象思维与创新能力培养
  • 线程(1)
  • 方达炬〖发明超新技术〗:冰堆技术;冷极冰堆建筑技术;
  • Item6--若不想使用编译器自动生成的函数,就该明确拒绝
  • 我发现LLM解析基因数据优化抗癌药剂量,患者副作用直降40%
  • 日记12.16
  • 论文AIGC查重率高怎么办?6个降AI率工具和技巧,AI率从100%降到3%! - 还在做实验的师兄
  • PCL曲面重建——为一组点云重建凸多边形/凹多边形
  • 信息与关系:涌现的三大核心原则
  • Linux文件权限
  • 28
  • 灵遁者:量子基元理论带来的新观点
  • 【补充】远程连接学校服务器操作说明
  • 本地私有知识库新选择:访答软件真实体验分享
  • 划分dp