当前位置: 首页 > 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 是一个强大的工具。

相关文章:

  • 深度学习 第4章 数值计算和 Deepseek 的实践
  • 【初学者】怎样学习、使用与研究算法?
  • 阅读《Vue.js设计与实现》 -- 02
  • 【Notepad】Notepad优化笔记AutoHotkey语法高亮\设置替换默认的notepad程序\设置主题\增加返回上一个编辑地方插件
  • Android 12系统源码_系统启动(一)init进程
  • 配置阿里云yum源
  • 算法模型从入门到起飞系列——深度优先遍历(DFS)
  • 数据无忧:自动备份策略全解析
  • Java 集合框架
  • 基于FPGA的DDS连续FFT 仿真验证
  • Ubuntu Qt: no service found for - “org.qt-project.qt.mediaplayer“
  • 第14周-Seq2Seq模型-NLP
  • 新型教材≠免设计课程
  • yt-dlp工具下载视频使用方法
  • VMware上调整centos终端的背景颜色
  • navicat忘记已经连接过的数据库密码的操作步骤
  • 论文略读(2025.3.18-更新中)
  • 盘泰UV种植体:抗老化新科技,焕发种植牙新活力
  • Xilinx系列FPGA视频采集转HDMI2.0输出,基于HDMI 1.4/2.0 Transmitter Subsystem方案,提供6套工程源码和技术支持
  • 设计模式之建造者模式
  • 新希望去年归母净利润4.74亿同比增逾九成,营收降27%
  • 时代邻里:拟收购成都合达联行科技剩余20%股权
  • “五一”假期云南铁路预计发送旅客超330万人次
  • 刘非任中共浙江省委常委、杭州市委书记
  • 人民论坛:是民生小事,也是融合大势
  • 最高法:“盗链”属于信息网络传播行为,构成侵犯著作权罪