C++ 的 error_code 之三:自定义 error_condition

1 自定义 error_condition

​ 上一节我们实现了自定义的 error_code,通过定制 std::error_code 的内部机制,支持与系统相关错误码的隐式转换和直接比较,这一节我们再考虑一种情况。假设我们的支持库要同时支持 Windows 和 Linux 系统,设备厂家分别为两种操作系统提供了设备驱动程序,但是它们底层的错误码却并不一致。以访问被拒绝错误为例,Windows 系统上的设备驱动会返回 0xC0000005,而 Linux 系统上的设备驱动会返回 13。支持库要求同时支持两种操作系统,希望提供一种与设备无关的错误码定义,让支持库不需要根据操作系统的差异用两套代码判断错误码,这种情况就需要使用自定义的 error_condition。

​ 与 error_code 的设计一样,error_condition 也用了 pimpl 大法,表面就是一张皮,内部全看 error_category。

1.1 定义错误码

​ 根据需求,我们要定义一组与系统无关的错误码,着需要有一定的抽象思考。作为一个简单的例子,我们将两种系统底层错误码抽象为以下两个:

enum class devsys_error {device_busy = 1,no_permission = 2
};

因为是定义系统无关的错误码,所以不用考虑跟底层错误码冲突的问题,我们简单用非 0 的值为它们编码。接着要提供 is_error_condition_enum<> 的特化版本,让系统知道我们的 devsys_error 是与系统无关的 error_condition 类型错误码:

template <>
struct std::is_error_condition_enum<devsys_error> : std::true_type {};

1.2 实现 error_category

​ 要支持 error_condition 的等价性判断,需要为自定义的 error_condition 提供与之相适应的 error_category。上一节已经介绍过 name() 接口和 message() 接口的实现方法,这里照样实现就行了:

struct devsys_errtab_t
{devsys_error code;const char* message;
};constexpr devsys_errtab_t devsys_errtab[] = {{devsys_error::device_busy, "Internal device is busy"},{devsys_error::no_permission, "Permission is deny"}
};class devsys_error_category : public std::error_category {
public:const char* name() const noexcept override {return "devsys_error";}std::string message(int condition) const override {for (const auto& entry : devsys_errtab) {if (static_cast<int>(entry.code) == condition) {return entry.message;}}return "unknown error";}
};

​ 本篇第二部分为实现自定义 error_code 而做的 dev_error_category 不需要考虑两个 equivalent() 接口的实现,因为我们自定义的 error_code 不需要考虑跟其他 error_condition 错误码进行等价性判断,使用 error_category 默认的行为就行了。但是要实现自定义的 error_condition,就需要考虑自身与其他 error_code 之间进行等价性判断,并且这个等价的定义要自己定义。定义等价性最简单的方法就是做个映射表,将不同平台的错误码值映射成我们定义的 devsys_error。根据之前的讨论,一个简单的映射表出现了:

struct devsys_map_t {int dcode;devsys_error icode;
};constexpr devsys_map_t devsys_map[] = {{ 0xC000002A, devsys_error::device_busy},{ 9, devsys_error::device_busy },{ 0xC0000005, devsys_error::no_permission},{ 13, devsys_error::no_permission }
};

错误码的值都是杜撰的,如有雷同,实属巧合。当然,也可以根据平台的差异分别定义 devsys_map,比如:

#ifdef _WIN32
constexpr devsys_map_t devsys_map[] = {{ 0xC000002A, devsys_error::device_busy},{ 0xC0000005, devsys_error::no_permission}
};
#else
constexpr devsys_map_t devsys_map[] = {{ 9, devsys_error::device_busy },{ 13, devsys_error::no_permission }
};
#endif

好处就是减少了映射表的大小,查找的时候效率更高一点。

​ equivalent() 接口有两个重载形式,第一个用于判断一个系统相关的错误码值与一个 error_condition 对象实例是否等价。实现的方法就是遍历 devsys_map,找到与 code 等值的映射关系,看看映射关系中的 devsys_error 是否与给定的 error_condition 相等:

bool equivalent(int code, const std::error_condition& condition) const noexcept override {for (const auto& entry : devsys_map) {if (entry.dcode == code) {return (condition == std::error_condition(entry.icode));}}return false;
}

error_condition() 的构造函数根据查找的映射关系中的 devsys_error 构造一个 error_condition() 对象实例,然后与第二个参数给出的 condition 对象比较,两个 error_condition 之间的比较是严格等值比较,这是 error_condition 自身的行为,没有用到 error_category。第二个 equivalent() 接口用于判断一个 error_code 对象实例与一个系统无关的错误码值是否等价。因为要与 error_code 对象进行比较,我们需要将映射表中的错误码(dcode)转换成 error_code 对象,然后与 code 参数做严格等值比较查找映射条目。如果找到映射条目,就比较相对应的系统无关错误码(icode)是否与第二个参数 condition 一致:

bool equivalent(const std::error_code& code, int condition) const noexcept override {for (const auto& entry : devsys_map) {if (std::error_code(entry.dcode, std::system_category()) == code) {return (static_cast<int>(entry.icode) == condition);}}return false;
}

​ 这个例子中,我们没有自定义 error_code,所以构造 error_code 的时候使用了 C++ 的 system_category 分类,其实这里 system_category 只是相当于一个占位符,因为 error_code 之间比较用不到 error_category(既然是占位符,使用 generic_category 或 iostream_category 都可以)。当需要与 error_condition 比较的时候,error_condition 的 devsys_error_category 分类就参与进来了。其实如果严谨一点,或者正式一点的设计,比如考虑跨平台发开,一般都要同时自定义 error_code 和 error_condition。如果同时自定义了 error_code,这里的判断就不需要这么复杂,可以直接将 entry.dcode 与 code 参数做 == 判断( if(entry.dcode == code) ),因为自定义的 error_code 构造函数会将 dcode 隐式转换成 error_code(当然,dcode 就不能用整数类型了)。

​ 最后,我们也要提供一个全局函数,返回一个 devsys_error_category 引用,下一节实现 make_error_condition() 函数的时候会用到它:

const std::error_category& devsys_category() {static devsys_error_category instance;return instance;
}

1.3 满足 error_condition 构造

​ 根据 error_condition 的构造函数,将一个 devsys_error 类型错误码枚举值隐式转型成 error_condition 对象需要满足两个条件,第一个是提供 is_error_condition_enum<devsys_error> 特化版本定义,这个我们在 3.1 节已经给出。第二个条件就是提供 make_error_condition() 函数针对 devsys_error 类型的重载实现。当错误码类型是 devsys_error 时,用 devsys_category 构造一个 error_condition 对象:

std::error_condition make_error_condition(devsys_error e) noexcept {return std::error_condition( static_cast<int>(e),devsys_category() );
}

1.4 验证一下

​ 现在假设我们的支持库中有一个功能函数,可能返回与系统相关的错误码,为了测试方便,我们通过 code 参数控制它返回的错误码的值:

bool MyOperator2(int code, std::error_code& ec) {ec = std::error_code(code, std::system_category());return false;
}

注意这里构造与系统相关的错误码时也用了 system_category,原因 1.2 小节已经介绍过了,因为我们这个例子没有使用自定义 error_code,所以借用 system_category 类型做占位符构建与系统相关的错误码。其实用任何 error_category 类型都可以,只要与 equivalent() 函数一致就行了。

​ 测试代码如下:

std::error_code ec1, ec2;
MyOperator2(0xC0000005, ec1);
MyOperator2(13, ec2);assert(ec1 != ec2);
assert(ec1 == devsys_error::no_permission);
assert(ec2 == devsys_error::no_permission);

正如我们预期的那样,ec1 和 ec2 虽然都是底层可能返回的权限拒绝错误码,但是它们是与系统相关的,所以 ec1 不等于 ec2。但是由于我们自定义 error_condition,以及相应的 devsys_category 提供了等价性判断,使得 ec1 和 ec2 都被认为是没有操作权限的错误。

2 总结

​ 通过自定义 error_code 和 error_condition 这两个例子,我们将 C++ 的 error_code 的设计灵活性具体展现给大家。error_code 的两个原则就是既支持“用户可扩展”,又能够“保留原始错误代码”的详细信息。标准库的用户能够添加自己的错误源,可能用于集成第三方库,也可用于创建更高级别的错误抽象定义。与此同时,程序员将使用系统底层的错误代码进行日志记录和跟踪,在诊断问题时可能起到至关重要的作用。比如上一节的例子,向上可以统一报告用户没有操作权限的错误,向下可以将 ec1 或 ec2 中具体的错误码写入 log,以方便诊断错误的具体原因。

参考资料

[1] Christopher Kohlhoff. system-error-support-in-c0x.

[2] N2241: Diagnostics Enhancements for C++0x (Rev. 1)

[3] https://en.cppreference.com/w/cpp/error/error_code

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/65997.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯——物联网设计与开发】Part1:GPIO

目录 一、GPIO输出——LED &#xff08;1&#xff09;资源介绍 &#x1f505;原理图 &#x1f505;驱动原理 &#xff08;2&#xff09;STM32CubeMX 软件配置 &#xff08;3&#xff09;代码编写 &#x1f7e2;️main 函数 &#xff08;4&#xff09;实验现象 二…

小程序发版后,强制更新为最新版本

为什么要强制更新为最新版本&#xff1f; 在小程序的开发和运营过程中&#xff0c;强制用户更新到最新版本是一项重要的策略&#xff0c;能够有效提升用户体验并保障系统的稳定性与安全性。以下是一些主要原因&#xff1a; 1. 功能兼容 新功能或服务通常需要最新版本的支持&…

Servlet解析

概念 Servlet是运行在服务端的小程序&#xff08;Server Applet)&#xff0c;可以处理客户端的请求并返回响应&#xff0c;主要用于构建动态的Web应用&#xff0c;是SpringMVC的基础。 生命周期 加载和初始化 默认在客户端第一次请求加载到容器中&#xff0c;通过反射实例化…

太速科技-633-4通道2Gsps 14bit AD采集PCie卡

4通道2Gsps 14bit AD采集PCie卡 一、板卡概述 二、性能指标 板卡功能 参数 内容 ADC 芯片型号 AD9689 路数 4路ADC&#xff0c; 采样率 2Gsps 数据位 14bit 数字接口 JESD204B 模拟接口 交流耦合 模拟输入 1V 连接器 6路 SMA 输入阻抗 50Ω 模拟指…

戴尔/Dell 电脑按什么快捷键可以进入 Bios 设置界面?

BIOS&#xff08;基本输入输出系统&#xff09;是计算机硬件与操作系统之间的桥梁&#xff0c;它负责初始化和测试系统硬件组件&#xff0c;并加载启动操作系统。在某些情况下&#xff0c;如调整启动顺序、更改系统时间或日期、修改硬件配置等&#xff0c;您可能需要进入BIOS进…

分类模型评估利器-混淆矩阵

相关文章 地理时空动态模拟工具介绍&#xff08;上&#xff09; 地理时空动态模拟工具介绍&#xff08;下&#xff09;地理时空动态模拟工具的使用方法 前言 混淆矩阵&#xff08;Confusion Matrix&#xff09;是机器学习领域中用于评估分类模型性能的一种工具。它通过矩阵的…

贪心算法概述

贪心算法总是作出当前看来最好的选择&#xff0c;是局部最优 可以使用贪心算法的问题一般具有两个重要的性质 贪心选择性质最优子结构性质 贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择来达到 其与动态规划的问题区别在于&#xff0c;动态规划算法通…

Unity-Mirror网络框架-从入门到精通之Basic示例

文章目录 前言Basic示例场景元素预制体元素代码逻辑BasicNetManagerPlayer逻辑SyncVars属性Server逻辑Client逻辑 PlayerUI逻辑 最后 前言 在现代游戏开发中&#xff0c;网络功能日益成为提升游戏体验的关键组成部分。Mirror是一个用于Unity的开源网络框架&#xff0c;专为多人…

wx015基于springboot+vue+uniapp的经济新闻资讯的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

CSS 中 content换行符实现打点 loading 正在加载中的效果

我们动态加载页面内容的时候&#xff0c;经常会使用“正在加载中…”这几个字&#xff0c;基本上&#xff0c;后面的 3 个点都是静态的。静态的问题在于&#xff0c;如果网络不流畅&#xff0c;加载时间比较长&#xff0c;就会给人有假死的 感觉&#xff0c;但是&#xff0c;如…

ESLint+Prettier的配置

ESLintPrettier的配置 安装插件 ​​​​​​ 在settings.json中写下配置 {// tab自动转换标签"emmet.triggerExpansionOnTab": true,"workbench.colorTheme": "Default Dark","editor.tabSize": 2,"editor.fontSize": …

Windows系统下载、部署Node.js与npm环境的方法

本文介绍在Windows电脑中&#xff0c;下载、安装并配置Node.js环境与npm包管理工具的方法。 Node.js是一个基于Chrome V8引擎的JavaScript运行时环境&#xff0c;其允许开发者使用JavaScript编写命令行工具和服务器端脚本。而npm&#xff08;Node Package Manager&#xff09;则…

Ubuntu 24.04 LTS 解决网络连接问题

1. 问题描述 现象&#xff1a;ens33 网络接口无法获取 IPv4 地址&#xff0c;导致网络不可用。初步排查&#xff1a; 运行 ip a&#xff0c;发现 ens33 接口没有分配 IPv4 地址。运行 ping www.baidu.com&#xff0c;提示“网络不可达”。查看 NetworkManager 日志&#xff0c…

Tauri2+Leptos开发桌面应用--Sqlite数据库操作

在之前工作&#xff08;使用Tauri Leptos开发带系统托盘桌面应用-CSDN博客&#xff09;的基础上&#xff0c;继续尝试对本地Sqlite数据库进行读、写、删除操作&#xff0c;开发环境还是VS CodeRust-analyzer。 最终程序界面如下&#xff1a; 主要参考文章&#xff1a;Building…

每日一些题

题解开始之前&#xff0c;给大家安利一个上班偷偷学习的好搭档&#xff0c;idea中的插件有一个叫 LeetCode with labuladong&#xff0c;可以在idea中直接刷力扣的题目。 朋友们上班没事的时候&#xff0c;可以偷偷摸几题。看八股的话&#xff0c;可以用面试鸭&#xff0c;也是…

Docker--Docker Container(容器) 之 操作实例

容器的基本操作 容器的操作步骤其实很简单&#xff0c;根据拉取的镜像&#xff0c;进行启动&#xff0c;后可以查看容器&#xff0c;不用时停止容器&#xff0c;删除容器。 下面简单演示操作步骤 1.创建并运行容器 例如&#xff0c;创建一个名为"my-nginx"的交互…

高频 SQL 50 题(基础版)_1068. 产品销售分析 I

销售表 Sales&#xff1a; (sale_id, year) 是销售表 Sales 的主键&#xff08;具有唯一值的列的组合&#xff09;。 product_id 是关联到产品表 Product 的外键&#xff08;reference 列&#xff09;。 该表的每一行显示 product_id 在某一年的销售情况。 注意: price 表示每…

linux进阶

目录 变量 shell变量 环境变量 预定义变量 位置变量 其他 管道与重定向 管道 重定向 shell脚本 分支结构 循环结构 数组 脚本实例 变量 shell变量 shell变量&#xff1a;shell程序在内存中存储数据的容器 shell变量的设置&#xff1a;colorred 将命令的结果赋值…

“TypeScript版:数据结构与算法-初识算法“

引言 在算法与编程的广阔世界里&#xff0c;总有一些作品以其独特的魅力和卓越的设计脱颖而出&#xff0c;成为我们学习和研究的典范。今天&#xff0c;我非常荣幸地向大家分享一个令人印象深刻的算法——Hello算法。 Hello算法不仅展现了作者深厚的编程功底&#xff0c;更以…

【复盘】2024年终总结

工作 重构风控系统 今年上半年其实就是整体重构系统&#xff0c;经历了多次加班的&#xff0c;其中的辛酸苦辣只有自己知道&#xff0c;现在来看的话&#xff0c;其实对自己还有一定的成长&#xff0c;从这件事情上也明白 绩效能不能拿到A&#xff0c;在分配的任务的时候就决…