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

std::expected

        C++23 引入的一个模板类,用于显式表示可能成功返回一个值或失败返回一个错误的操作。它结合了类型安全和错误处理的优点,是传统错误码(error code)和异常(exceptions)的替代方案。

        错误处理的逻辑关系为条件关系,若正确,则执行A 逻辑;若失败,则执行B 逻辑,并需要知
道确切的错误信息,才能对症下药。当前的常用方式是通过错误码或异常,但使用起来还是多有不
便。
std::expected<T, E> 表示期望,算是std::variant 和std::optional 的结合,它要么保留T(期望的类型),要么保留E(错误的类型),它的接口又和std::optional 相似。

核心概念

  • 设计目标:显式表示可能失败的操作,避免隐式错误处理(如异常)或全局错误码的弊端。

  • 两种状态

    • 预期值(Expected Value):操作成功时存储的返回值。

    • 错误(Error):操作失败时存储的错误信息。

  • 类型安全:错误和成功值的类型在编译时确定,避免运行时类型错误。

std::expected

在标头 <expected> 定义

template< class T, class E >
class expected;

(1)(C++23 起)
template< class T, class E >

    requires std::is_void_v<T>

class expected<T, E>;
(2)(C++23 起)

类模板 std::expected 提供表示两个值之一的方式:它要么表示一个 T 类型的预期 值,要么表示一个 E 类型的非预期 值。expected 决不会无值。

1) 主模板。在自身的存储中包含预期值或非预期值,该值内嵌于 expected 对象。

2) void 部分特化。表示一个 void 类型的预期值或在自身的存储中包含非预期值。如果包含非预期值,那么该值内嵌于 expected 对象。

如果程序以引用类型、函数类型,或 std::unexpected 的特化实例化 expected,那么程序非良构。另外,T 必须不是 std::in_place_t 或 std::unexpect_t。

模板形参

T-预期值的类型。类型必须是(可有 cv 限定的)void,或者符合可析构 (Destructible) 要求(尤其是不允许数组或引用类型)。
E-非预期值的类型。类型必须符合可析构 (Destructible) 要求,且必须对于 std::unexpected 为合法的模板实参(尤其是不允许数组、非对象类型及 cv 限定的类型)。

成员类型

成员类型定义
value_typeT
error_typeE
unexpected_typestd::unexpected<E>

成员别名模板

类型定义
rebind<U>std::expected<U, error_type>

数据成员

成员定义
bool has_valexpected 对象当前是否表示预期值
(仅用于阐述的成员对象*)
T val (仅限主模板)预期值
(仅用于阐述的变体成员对象*)
E unex非预期值
(仅用于阐述的变体成员对象*)

成员函数

(构造函数)

构造 expected 对象
(公开成员函数)

(析构函数)

销毁 expected 对象以及其所含的值
(公开成员函数)

operator=

赋值内容
(公开成员函数)
观察器

operator->operator*

访问预期值
(公开成员函数)

operator boolhas_value

检查对象是否含有预期值
(公开成员函数)

value

返回预期值
(公开成员函数)

error

返回非预期值
(公开成员函数)

value_or

如果有预期值则返回它,否则返回另一个值
(公开成员函数)

error_or

如果有非预期值则返回它,否则返回另一个值
(公开成员函数)
单子操作

and_then

若存在预期值则返回给定的函数在其上的结果,否则返回 expected 本身
(公开成员函数)

transform

若存在预期值则返回含有变换后的预期值的 expected,否则返回 expected 本身
(公开成员函数)

or_else

若 expected 含有预期值则返回其自身,否则返回给定的函数在非预期值上的结果
(公开成员函数)

transform_error

若含有预期值则返回 expected 本身,否则返回含有变换后非预期值的 expected
(公开成员函数)
修改器

emplace

原位构造预期值
(公开成员函数)

swap

交换内容
(公开成员函数)

非成员函数

operator==

(C++23)

比较 expected 对象
(函数模板)

swap(std::expected)

(C++23)

特化 std::swap 算法
(函数)

辅助类

unexpected

(C++23)

表示一个非预期值
(类模板)

bad_expected_access

(C++23)

指示对含有非预期值的 expected 的有检查访问的异常
(类模板)

unexpect_tunexpect

(C++23)

expected 中非预期值的原位构造标签
(类) (常量)

基本用法

1. 定义与初始化
#include <expected>
#include <string>// 定义一个可能返回 int 或字符串错误的 expected 类型
std::expected<int, std::string> parse_number(const std::string& input) {try {return std::stoi(input);} catch (...) {return std::unexpected("Invalid number format");}
}
2. 检查状态
auto result = parse_number("123");
if (result.has_value()) 
{std::cout << "Value: " << *result << std::endl;
} 
else 
{std::cout << "Error: " << result.error() << std::endl;
}
3. 访问值或错误
  • 直接访问

int value = result.value();      // 成功时返回值,失败时抛出 std::bad_expected_access
std::string err = result.error();// 失败时返回错误

安全访问

int value = result.value_or(0);  // 失败时返回默认值 0
4. transform:转换成功值
  • 功能:若 std::expected 包含成功值,则对其应用一个函数,返回新的 std::expected;若包含错误,则直接传递错误。
  • 适用场景:对成功值进行纯转换(不涉及可能失败的操作)。
示例:将整数结果转换为字符串
#include <expected>
#include <string>
#include <iostream>std::expected<int, std::string> computeValue(bool success) 
{if (success){return 42;}return std::unexpected("Error");
}int main() 
{auto result = computeValue(true).transform([](int x) { return "Answer: " + std::to_string(x*2); });if (result) {std::cout << *result << "\n"; // 输出: Answer: 42} else {std::cout << result.error() << "\n";}
}
5. and_then:链式执行可能失败的操作
  • 功能:若 std::expected 包含成功值,则对其应用一个返回新 std::expected返回值类型的函数;若包含错误,直接传递错误。
  • 适用场景:需要连续执行多个可能失败的操作(例如:先读取文件,再解析内容)。
示例:链式除法操作
#include <expected>
#include <string>
#include <iostream>std::expected<double, std::string> safeDivide(double a, double b) 
{if (b != 0){return a / b;}return std::unexpected("Division by zero");
}int main() 
{auto result = safeDivide(10, 2).and_then([](double x) { return safeDivide(x, 5); }) // 10/2=5 → 5/5=1.and_then([](double x) { return safeDivide(x, 0); }); // 1/0 → 错误if (result) {std::cout << *result << "\n";} else {std::cout << "Error: " << result.error() << "\n"; // 输出: Error: Division by zero}
}
6. or_else:处理错误
  • 功能:若 std::expected 包含错误,则对其应用一个处理函数(返回新 std::expected);若包含成功值,直接传递成功值。
  • 适用场景:错误恢复、日志记录或提供默认值。
示例:错误恢复和日志记录
#include <expected>
#include <string>
#include <iostream>std::expected<int, std::string> readConfig() {return std::unexpected("Config file missing");
}int main() {auto result = readConfig().or_else([](const auto& error) {std::cerr << "Log: " << error << "\n"; // 记录错误日志return std::expected<int, std::string>(100); // 提供默认值});std::cout << "Final value: " << *result << "\n"; // 输出: Final value: 100
}

一个简单的例子:

enum class Status : uint8_t
{Ok,connection_error,no_authority,format_error,
};bool connected() 
{return true;
}bool has_authority() 
{return false;
}bool format() 
{return false;
}std::expected<std::string, Status> read_data() 
{if (!connected())return std::unexpected<Status> { Status::connection_error };if (!has_authority())return std::unexpected<Status> { Status::no_authority };if (!format())return std::unexpected<Status> { Status::format_error };return {"my expected type"};
}int main() 
{auto result = read_data();if (result) {std::cout << result.value() << "\n";} else {std::cout << "error code: " << (int)result.error() << "\n";}
}

 这种方式无疑会简化错误处理的操作。

示例场景

1. 文件读取
#include <fstream>
#include <vector>std::expected<std::vector<char>, std::string> read_file(const std::string& path) 
{std::ifstream file(path, std::ios::binary);if (!file) {return std::unexpected("Failed to open file");}std::vector<char> data;file.seekg(0, std::ios::end);data.resize(file.tellg());file.seekg(0, std::ios::beg);file.read(data.data(), data.size());if (file.fail()) {return std::unexpected("Failed to read file");}return data;
}// 使用示例
auto data = read_file("config.txt");
if (data) 
{process_data(*data);
} 
else 
{log_error(data.error());
}
2. 数学计算
std::expected<double, std::string> safe_divide(double a, double b) 
{if (b == 0) {return std::unexpected("Division by zero");}return a / b;
}// 使用示例
auto result = safe_divide(10, 2);
if (result) 
{std::cout << "Result: " << *result << std::endl;
}

Monadic 操作(C++23)

std::expected 支持链式操作,类似函数式编程中的 map 和 and_then

#include <iostream>
#include <type_traits>
#include <utility> // for std::forward_like
#include <expected>std::expected<int, std::string> validate(int x) {if (x < 0) return std::unexpected("Negative value");return x;
}std::expected<int, std::string> process(int x) {return x * 2;
}int main()
{// 链式调用auto result = validate(42).and_then(process)  // 仅在成功时调用 process.transform([](int x) { return x + 1; }); // 转换值if (result) {std::cout << "Final value: " << *result << std::endl; // 输出 85}
}

对比其他错误处理方式

方式优点缺点
异常自动传播错误性能开销,控制流不透明
错误码无性能开销易被忽略,类型不安全
std::expected显式错误,类型安全,性能高效需要手动处理错误状态

注意事项

  1. 错误类型:错误类型可以是任意类型(如 std::error_code、自定义枚举或字符串)。

  2. 性能std::expected 无动态内存分配,适合性能敏感场景。

  3. 与异常结合:可以包装可能抛出异常的操作(如示例中的 parse_number)。

  4. 编译器支持:需要支持 C++23 的编译器(如 GCC 13、Clang 16+)。


总结

std::expected 提供了一种类型安全、显式的错误处理机制,适用于替代传统错误码或异常的场景。它通过模板参数明确区分成功值和错误类型,结合 Monadic 操作可以编写更清晰的链式逻辑。对于需要高性能和可预测性的代码(如库开发或嵌入式系统),std::expected 是一个强大的工具。


http://www.mrgr.cn/news/95116.html

相关文章:

  • [入门]NUC13配置Ubuntu20.04详细步骤
  • 让AI看见世界:MCP协议与服务器的工作原理
  • AI学习——卷积神经网络(CNN)入门
  • P2786 英语1(eng1)- 英语作文
  • STM32原理性知识
  • SAP 附件增删改查与文件服务器交互应用
  • dijkstra算法——47. 参加科学大会
  • PostgreSQL:语言基础与数据库操作
  • 数据大屏标题加载顶部流光
  • LEDNet总结
  • Python Pyecharts面试题及参考答案
  • 数据结构-------栈
  • 【C++】动态规划从入门到精通
  • 详解Sympy:符号计算利器
  • MySQL 调优
  • Firebase崩溃:ViewBinding not init!!
  • Quartus + VScode 实现模块化流水灯
  • MySQL 入门大全:查询语言分类
  • 【免费网址/插件】视频和图片数据采集推荐~
  • Python散点图(Scatt Plot):数据探索的“第一张图表”