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

CppCon 2015 学习:Improving the future<T> with monads

SolidFire 系统问题空间解析:

在 SolidFire 的数据存储架构中,以下是其工作流程和相关的关键挑战:

  1. 通过 iSCSI 请求数据
    • 数据请求通过 iSCSI 协议进入系统。这是一个用于连接存储设备并在网络上传输数据的协议。通过此协议,外部系统能够发出数据请求。
  2. 查找内部 ID
    • 系统会 查找内部 ID,这是一个映射过程,将外部请求的目标转换为内部存储中的唯一标识符。这样,系统才能精确地定位到数据所在的地方。
  3. 数据是否在本地缓存中
    • 本地缓存 是一个高速存储区域,用于存放最近或频繁访问的数据。如果数据已经存在于本地缓存中,那么响应速度会非常快。
  4. 从其他服务获取数据
    • 如果请求的数据不在本地缓存中,系统将从其他存储服务中 获取数据块。由于 SolidFire 是一个分布式系统,数据可能存储在不同的服务或节点上,系统需要协调从多个地方拉取数据。
  5. 用户可见的延迟
    • 用户看到的延迟是由整个请求链中 响应最慢的服务 决定的。如果其中任何一个服务(例如,缓存查找、数据块获取等)响应较慢,那么整个请求的延迟就会增加,从而影响最终用户的体验。

Travis Gockel (C++ Pirate SolidFire):

  • Travis Gockel 是 SolidFire 的一位工程师或关键人物,可能是系统开发的核心成员之一。他的称号 “C++ Pirate” 暗示他在 C++ 编程方面有着深厚的专业背景,并且可能以一种幽默或俏皮的方式表现出他的技术特长。

关于元数据 (Metadata) 和编码 (encode()):

  1. 元数据
    • 元数据包含了很多关于数据存储的关键信息,比如 卷偏移量 (volumeoffset)大小 (size) 等信息。这些元数据帮助 SolidFire 定位到具体的数据块位置,从而可以高效地管理和调取数据。
  2. 编码过程 (encode())
    • encode() 可能是指在存储或传输数据时进行的 编码操作。在这个过程中,系统可能会对数据块或元数据进行一定的转换,以确保数据能够以合适的格式存储或者被其他服务理解。

总结:

  1. 数据请求流:通过 iSCSI 进入系统 → 查找内部 ID → 检查本地缓存 → 如果没有则从其他服务获取数据 → 受最慢响应服务影响的用户延迟。
  2. 系统效率的挑战:确保即使面对分布式存储和硬件故障,系统仍能快速、稳定地响应请求。
  3. 元数据和编码的作用:元数据帮助定位和管理数据块,编码过程确保数据按正确格式处理。
    如果你对这部分内容有更深入的疑问或者需要进一步的解释,随时告诉我!

future<T> 的目标是什么?

future<T> 的目标是提供一种 异步通信机制,用于简化 并发编程。具体来说,它的作用是使得在并发程序中,任务的执行和结果的获取能够更加高效和清晰。

关键点:
  1. 异步通信future<T> 允许 提供者(promise)接收者(future) 之间进行异步通信。也就是说,任务的执行和结果的获取是分开的,程序不需要等待任务完成才能继续执行其他操作。
  2. 提供者(promise):提供者是执行任务的部分,通常会发出一个任务,并返回一个 promise,表示这个任务最终会提供一个结果。
  3. 接收者(future):接收者是等待结果的部分。它通过 future<T> 获取任务的最终结果,但不需要等待任务的完成,可以继续执行其他任务,等结果准备好时再进行处理。
  4. 简化并发编程:这种机制让并发编程变得更加容易,因为它提供了一种简单的方式来处理多个并行执行的任务,而不需要复杂的回调函数或线程同步机制。
总结:

future<T> 通过将任务分为执行和获取结果两个阶段,帮助程序员更容易地处理并发和异步操作。
希望这个解释清楚!如果有更多问题,随时问我。

std::future<T> 的介绍和目的

std::future<T> Purpose template <typename T>
class future {
public:T get();bool valid() const noexcept;void wait() const;template <typename TRep, typename TPeriod>future_status wait_for(const chrono::duration<TRep, TPeriod>& rel_time) const;template <typename TClock, typename TDuration>future_status wait_until(const chrono::time_point<TClock, TDuration>& abs_time) const;shared_future<T> share();
};

std::future<T> 是 C++ 中一个非常重要的类,它用于表示异步操作的结果。它允许程序在等待某个任务完成时,不阻塞当前线程,从而提高并发编程的效率。

目的

std::future<T> 的主要目的是提供一种机制,能够获取异步任务的结果,并且允许程序在等待这些结果的同时继续执行其他操作。

std::future<T> 类的成员函数

1. T get()
  • 作用:获取异步操作的结果。如果操作已经完成,get() 会返回任务的结果;如果任务还没有完成,调用此函数会阻塞当前线程,直到结果准备好。
  • 返回值:返回类型为 T,即任务的结果类型。
2. bool valid() const noexcept
  • 作用:检查 future 是否有效。如果任务已经完成并且结果可以被获取,返回 true;否则返回 false
  • 例子:如果 future 对象已经被移动或者没有与任何任务关联,则它是无效的。
3. void wait() const
  • 作用:阻塞当前线程,直到异步任务完成并返回结果。如果任务已经完成,wait() 立即返回;如果没有完成,它将一直等待,直到任务完成为止。
4. template <typename TRep, typename TPeriod> future_status wait_for(const chrono::duration<TRep, TPeriod>& rel_time) const
  • 作用:在指定的时间内等待任务完成。如果在给定的时间限制内任务完成,返回 future_status::ready;如果超时,则返回 future_status::timeout
  • 参数rel_time 是一个相对的时间段,用来指定最多等待的时间。
  • 返回值:返回 future_status 枚举值(readytimeoutdeferred)。
5. template <typename TClock, typename TDuration> future_status wait_until(const chrono::time_point<TClock, TDuration>& abs_time) const
  • 作用:等待直到指定的绝对时间点。如果在给定时间点前任务完成,返回 future_status::ready;如果任务超时,则返回 future_status::timeout
  • 参数abs_time 是一个绝对时间点,表示等待的最大时间点。
  • 返回值:返回 future_status 枚举值(readytimeoutdeferred)。
6. shared_future<T> share()
  • 作用:返回一个 shared_future<T>,这是一个能够共享的 future 对象。shared_future 允许多个线程共享同一个任务结果。
  • 使用场景:如果需要多个线程同时获取同一个异步任务的结果,可以使用 shared_future 来实现。

总结:

std::future<T> 提供了一个用于获取异步任务结果的机制,并且提供了多种方法来控制线程的行为,支持等待任务的完成和处理超时等情况。它是 C++ 中并发编程中的一个重要工具,帮助程序员更方便地处理异步操作。

如何使用 std::future<T>

在你提供的代码中,使用 std::future<T> 来进行 异步操作,以便并行地读取数据并合并结果。这是通过 std::async 来启动异步任务,并通过 future<T> 获取结果。
让我们逐步分析代码,并解释它的工作原理:

代码解析:

buffer read_multi(location x, location y) {// 启动一个异步任务来读取 x 位置的数据,并返回一个 future<buffer> 对象future<buffer> fut_z1 = async(&read, x);// 同时,在主线程中同步地读取 y 位置的数据buffer z2 = read(y);// 等待异步任务完成,获取异步任务的结果并与 z2 合并return fut_z1.get() + z2;
}
1. future<buffer> fut_z1 = async(&read, x);
  • 作用:调用 std::async 函数启动一个异步任务。在这里,async 会启动一个新线程去执行 read(x) 函数,并返回一个 future<buffer> 对象 fut_z1。这个异步任务会在后台读取 x 位置的数据,并将结果存储在 fut_z1 对象中。
  • async 的工作原理async 会异步执行函数(此处为 read)并立即返回一个 future 对象。future 会表示一个异步操作的最终结果,可以通过 .get() 方法获取结果。
2. buffer z2 = read(y);
  • 作用:在主线程中同步读取 y 位置的数据。这是一个普通的同步操作,程序会等 read(y) 完成并返回 buffer z2
3. return fut_z1.get() + z2;
  • 作用:调用 fut_z1.get() 获取异步任务的结果。.get() 会阻塞当前线程,直到 fut_z1 中的任务完成并返回 buffer 结果。获取结果后,将 fut_z1.get()z2 合并并返回。
  • fut_z1.get():该方法会阻塞当前线程,直到异步任务完成并返回结果。如果异步任务已经完成,get() 会立即返回结果;如果任务还没完成,它将等待直到结果准备好。

then 函数的使用

在 C++ 中,then 是一种扩展 std::future<T> 的方式,用于在异步操作完成之后继续执行一个回调函数。这种方式实现了链式操作,即将异步操作和后续处理逻辑链接在一起。

1. then 函数的定义
template <typename F>
auto then(F&& continuation) -> future<decltype(continuation(*this))>;
  • then 方法的作用是接收一个回调函数 continuation,并在当前 future 完成时执行这个回调函数。
  • then 会返回一个新的 future,这个 future 包含的是回调函数执行后的结果。
关键点:
  • F&& continuation:传入的回调函数 continuation 是一个函数对象,可以是任何类型的可调用对象(如 lambda 表达式、函数指针等)。
  • decltype(continuation(*this)):这是通过回调函数返回类型的推导,*this 代表当前 future 对象的值。
  • 返回的 future<decltype(continuation(*this))> 类型表示回调函数执行后的结果,也就是说,then 返回一个新的 future,它将会存储回调的结果。
2. 示例代码解析
future<int> x = async(&thing);  // 启动异步操作,返回 future<int> x
future<double> y = x.then([](future<int> f) {return double(f.get());  // 从 future<int> 获取结果,并转换为 double
});

步骤解释

  1. 启动异步任务
    • async(&thing) 启动异步任务 thing,并返回一个 future<int> 类型的 x,表示异步任务的结果类型是 int
  2. 调用 then
    • x.then(...) 表示在 x 的异步操作完成之后执行一个新的回调函数。回调函数接受一个 future<int> 类型的参数 f(这是 x 对应的 future 对象)。
  3. 回调函数的执行
    • 在回调函数内部,调用 f.get() 获取 future<int> 对象 f 中存储的值(即异步任务的返回值)。然后,将 int 转换为 double 类型并返回。
  4. 返回新的 future<double>
    • 由于回调函数返回 double 类型的结果,then 方法会返回一个新的 future<double> 类型的 y,表示 x 完成后返回的转换结果。
3. 如何工作
  • 链式操作then 允许将多个异步操作串联起来。在第一个异步操作完成后,then 会立即启动下一个异步操作。
  • 传递上下文:在回调函数内部,我们可以使用 get() 获取前一个 future 的结果,且不需要阻塞当前线程。
  • 类型推导decltype(continuation(*this)) 动态推导回调函数返回值的类型,以便返回正确类型的 future

解析:Awkward for Aggregation

在 C++ 中,std::future<T> 用于表示异步任务的结果。你提到的代码示例展示了如何处理多个异步任务并收集它们的结果。让我们逐步解析这个问题。

代码解析

std::vector<std::future<int>> all_tasks = foo();  // 获取多个异步任务
std::future<std::vector<std::future<int>>> all_done= when_all(make_move_iterator(begin(all_tasks)),make_move_iterator(end(all_tasks)));
1. std::vector<std::future<int>> all_tasks = foo();:
  • 这行代码通过调用 foo() 函数返回一个 std::vector<std::future<int>>。这个向量包含多个异步任务,每个任务的返回类型是 int
  • 假设 foo() 是一个函数,可能会生成多个异步任务,例如通过 std::async 或其他方式。
2. when_all(...)
  • when_all 是一个函数,它用于 等待多个异步任务的完成。它接受一个迭代器范围(例如 begin(all_tasks)end(all_tasks))并将这些异步任务传递给它。
  • when_all 返回一个 std::future<std::vector<std::future<int>>>,这个 future 对象包含了所有任务的 future 结果。也就是说,all_done 是一个包含多个 future<int> 对象的 future
    关键点:
  • when_all 本质上是对一组异步任务的集合进行同步,它等到所有任务完成后,返回一个包含所有 future 对象的向量。
3. make_move_iterator(begin(all_tasks)), make_move_iterator(end(all_tasks))
  • 这部分代码使用了 make_move_iterator移动 向量中的元素,而不是拷贝它们。这是为了避免不必要的拷贝,提升性能。all_tasks 中的每个 future<int> 都被移动到 when_all 函数中,而不是被拷贝。

哪个 future<T>::get 会阻塞?

在这个例子中,关键的问题是 哪个 future<T>::get() 会阻塞

  1. all_done.get() 会阻塞
    • all_done.get() 是唯一会真正阻塞的操作。为什么?因为 all_done 是一个 future<std::vector<std::future<int>>> 类型,它代表的是 所有异步任务的集合。当调用 get() 时,它会等待 所有异步任务 都完成。
    • all_done.get() 会阻塞,直到所有通过 when_all 聚合的 future<int> 都完成并返回结果。
  2. future<T>::get() 的阻塞机制
    • 每个 std::future<int> 都是一个异步任务的结果。当你调用 get() 时,如果任务尚未完成,get() 会阻塞直到任务完成。
    • 在这个场景中,当你访问 all_done 中的每个 future<int> 时,get() 可能会阻塞,直到每个单独的任务完成。

并发性和性能问题:

你提到了一些与性能相关的问题:

  1. future<T> 是多线程的,必须处理锁
    • future<T> 本身是为多线程设计的,它的 get() 函数可能会涉及到锁定机制,以确保在多线程环境下的安全访问。
    • 如果你在多个线程中并发调用 get(),可能会引发 锁竞争,导致性能下降。
  2. std::vector<int> 累加比 std::vector<std::future<int>> 更快
    • 处理 std::vector<int> 直接存储数据比处理 std::vector<std::future<int>> 更高效。这是因为 future<int> 是异步结果的表示,它本身需要额外的开销(例如锁、同步等),而直接操作 int 值不会有这些开销。
    • 如果你使用 std::vector<std::future<int>>,每次调用 get() 都会导致线程阻塞,等待异步任务完成,进而增加了额外的时间成本。
    • 相反,如果你首先等待所有异步任务完成,然后将其结果存储到一个 std::vector<int> 中,再进行后续处理,通常会更加高效。

如何优化:

  1. 减少 get() 的调用
    • 可以通过批量获取所有 future<int> 的结果,而不是每个任务单独调用 get(),这样可以减少锁竞争。
    • 一种优化方式是先使用 when_all 等待所有任务完成,然后批量处理结果,而不是逐个阻塞 get()
  2. 并发执行
    • 在设计并发任务时,尽量让任务能够并行执行,而不是让它们依赖于其他任务的结果。利用 when_all 聚合多个异步任务,确保在所有任务完成后再继续处理,避免中间阻塞。
  3. 使用 std::shared_future
    • 如果需要多个地方读取同一个 future 的结果,可以使用 std::shared_future,它支持多个线程共享结果,而不需要重复阻塞。

总结:

  • when_all 用于等待多个异步任务完成,并返回一个包含所有任务结果的 future
  • all_done.get() 会阻塞,因为它会等待所有异步任务完成,才返回结果。
  • 处理 std::vector<int> 会比处理 std::vector<std::future<int>> 更高效,避免了多次调用 get() 的性能开销。
  • 通过减少锁竞争、减少 get() 调用的频率,可以提高程序的并发性能。

代码解析与理解

你提供了两个代码片段,一个是嵌套的 try-catch 异常处理结构,另一个是使用 std::future.then() 方法的异步链式操作。我们将逐步分析这两个代码片段,并讨论它们的差异和使用场景。

1. 嵌套的 try-catch 异常处理结构:

try {try {try {foo();} catch (...) {throw;}bar();} catch (...) {throw;}baz();
} catch (const std::exception& ex) {report(ex);
}
解析
  • 这段代码实现了多层嵌套的异常处理机制。每一层都有一个 try 块和一个 catch 块,用来捕获异常。
  • 最内层的 try-catch:
    • 调用 foo(),如果发生任何异常(catch(...) 捕获所有异常),会重新抛出该异常(throw;)。不过这种重新抛出没有任何处理的意义,因为没有对异常进行任何修改或补充信息的操作。
  • 中间层的 try-catch:
    • 捕获异常并重新抛出,与上一层类似。
  • 外层的 try-catch:
    • 调用 baz(),如果发生异常,最终捕获该异常并执行 report(ex) 来报告 std::exception 类型的异常。
问题
  • 冗余异常处理:每一层 catch 都只是将捕获的异常重新抛出,这样的处理方式没有任何实际意义。通常我们会在捕获异常后进行某些处理(比如日志记录、资源清理、或是特定的恢复操作),而这里的多重 throw 并没有真正进行任何处理,导致代码冗长且没有实际作用。
优化
  • 将多层嵌套的 try-catch 简化为一个简单的结构,只需在外层捕获异常并进行报告,减少代码冗余。

2. 使用 std::future.then() 的异步链式操作:

foo_async().then([](std::future<void> x) {x.get(); return bar();}).then([](std::future<void> y) {y.get(); return baz();}).then([](std::future<void> z) {try {z.get();} catch (const std::exception& ex) {report(ex);}});
解析
  • 这段代码展示了使用 std::future 进行异步操作,并通过 .then() 来链接多个异步任务。每个 then() 都是一个异步操作的后续步骤。
  1. foo_async():
    • foo_async() 是一个返回 std::future<void> 的异步任务,它启动异步操作并返回一个 future<void>
  2. .then([](std::future<void> x) { x.get(); return bar(); }):
    • 这是第一个 then() 链接的操作。它接受一个 future<void> 对象 x,并调用 x.get() 等待异步任务 foo_async() 完成。
    • 调用 x.get() 会阻塞当前线程直到 foo_async() 完成,然后调用 bar() 进行下一个操作。这里的 return bar(); 意味着返回 bar() 的结果,它会被下一个 then() 处理。
  3. .then([](std::future<void> y) { y.get(); return baz(); }):
    • 第二个 then() 会在 bar() 完成后执行,等待上一个异步操作(bar())完成,接着执行 baz()
    • 同样的,它通过 y.get() 来等待前一个任务完成,之后继续执行 baz()
  4. .then([](std::future<void> z) { try { z.get(); } catch (const std::exception& ex) { report(ex); } }):
    • 最后一个 then() 等待 baz() 完成,并且包含异常处理。通过 z.get() 获取 baz() 的结果,若抛出异常,则通过 catch (const std::exception& ex) 来捕获并报告异常。
    • 这种结构确保了每一步的异常都能被捕获和报告。
关键点
  • 链式调用std::future::then() 允许你将多个异步任务连接成一个链式结构。每个任务的完成都会触发下一个任务的执行。
  • get() 的使用:每个 then() 中,调用 get() 来等待异步任务完成,阻塞当前线程直到结果准备好。
  • 异常处理:最后一个 then() 中通过 try-catch 捕获异常并调用 report(ex) 来报告异常。这样,异常被集中处理,避免了冗余的异常捕获。
优势
  • 简洁且易于管理:将多个异步任务通过 .then() 连接成链式调用,避免了重复的异常处理和控制流管理,使代码更加清晰。
  • 集中式异常处理:所有异步任务的异常处理都可以集中在最后一步 then() 中,避免了多次重复的 catch 块。

对比与总结

1. 多层嵌套的 try-catch
  • 冗余:嵌套的 try-catch 结构在很多情况下会显得冗余,特别是当每一层 catch 块只是简单地重新抛出异常时,实际并没有进行有效的错误处理。
  • 不可扩展性:如果未来需要对每个异常类型做更细致的处理,嵌套的 try-catch 结构可能会变得复杂且难以维护。
2. 使用 std::future.then() 进行异步链式调用
  • 简洁高效:通过 .then() 可以避免多重嵌套的异常处理,将异步任务和异常处理集中起来,更加简洁且易于扩展。
  • 灵活性:你可以方便地增加、删除或调整异步任务的顺序,只需要修改 then() 链中的顺序,而不需要改变整个异常处理流程。
  • 集中异常处理:异常处理通过 try-catch 集中在最后一个 then() 中,确保异常只在最后处理,避免了代码的重复和冗余。
推荐做法
  • 对于异步任务,使用 std::future.then() 链式调用是一种更加现代和高效的方式,它不仅可以简化代码结构,还能提高代码的可维护性。
  • 避免嵌套的 try-catch,尤其是当它没有有效处理异常时。尽量减少不必要的异常重新抛出,集中处理异常,提升代码清晰度和效率。

解析:expected<T, E> 类型

在你提供的代码中,expected<T, E> 是一种表示异步任务结果的类型,它结合了两种概念:

  1. 异步通信 (future<T>):通过 expected<T, E> 可以处理某个操作的异步结果。
  2. 错误报告:它不仅表示成功的结果,还能够处理和报告错误。
    这个设计的灵感来自于函数式编程中的“单子(monad)”概念,特别是 OptionEither 类型。它允许你通过链式调用处理可能失败的操作,而无需显式地处理错误或异常。

1. expected<T, E> 类定义

template <typename T, typename E> 
class expected {
public:auto status() const -> bool;  // 检查操作是否成功auto get() -> T;              // 获取成功的结果template <typename F> auto map(F f) -> expected<decltype(f(std::declval<T>()))>;  // 映射操作template <typename F> auto catch_error(F f) -> expected<std::common_type_t<T, decltype(f(std::declval<std::exception_ptr>()))>>;  // 错误处理
};
成员函数说明
  • status()
    • 用于检查操作是否成功,返回 true 表示成功,false 表示失败。这个方法类似于检查 std::future 的状态是否是有效的。
  • get()
    • 获取操作的成功结果。与 std::future<T>::get() 类似,get() 会返回存储的结果 T。如果失败,这可能会抛出错误。
  • map(F f)
    • 映射操作:接受一个函数 f,并将该函数应用到当前值 T 上。如果当前状态表示成功,那么返回一个新的 expected 对象,包含映射后的结果。
    • 这个方法的返回类型是 expected<decltype(f(std::declval<T>()))>,即应用 f 后的类型。
  • catch_error(F f)
    • 错误处理:如果当前状态表示失败(例如捕获到异常),它允许你通过函数 f 来处理错误。f 接受一个错误信息(例如 std::exception_ptr),并返回一个类型 T 的值,作为错误处理的结果。
    • 该方法的返回类型是一个新的 expected 对象,它的类型为 expected<std::common_type_t<T, decltype(f(std::declval<std::exception_ptr>()))>>,即错误处理后返回的类型。

2. 使用 expected<T, E> 的代码示例

expected<int> x = foo();  // foo() 返回一个期望值
expected<double> y = x.map([](int a) { return double(a); });  // 将 int 转换为 double
expected<double> z = y.catch_error([](auto) { return 0.0; });  // 错误时返回 0.0
expected<string> w = z.map([](double a) { return to_string(a); });  // 将 double 转换为 string
cout << w.get() << endl;  // 输出最终结果
逐步解析
  1. expected<int> x = foo();
    • 调用 foo() 返回一个 expected<int> 对象。这个对象代表一个异步操作的结果,成功时保存一个 int 类型的值,失败时则包含一个错误信息。
  2. expected<double> y = x.map([](int a) { return double(a); });
    • 使用 map() 方法将 x 中的 int 值转换为 double
    • 如果 x 是成功的,map() 会将 int 转换成 double,并返回一个新的 expected<double> 对象。
  3. expected<double> z = y.catch_error([](auto) { return 0.0; });
    • 调用 catch_error() 来处理可能的错误。如果 y 表示失败,那么错误处理函数 [] { return 0.0; } 会返回 0.0 作为错误的结果。
    • 如果 y 成功,则 catch_error() 不会做任何事情,直接返回 y
  4. expected<string> w = z.map([](double a) { return to_string(a); });
    • 使用 map()double 转换为 string
    • 如果 z 成功,map()double 值转换为字符串,并返回一个新的 expected<string>
  5. cout << w.get() << endl;
    • 最后调用 get() 来获取最终结果。如果所有操作都成功,w.get() 将返回一个 string,并打印到控制台。

3. expected<T, E> 的优势

  • 链式操作:你可以通过 map()catch_error() 等方法链式地组合多个操作,而无需显式地处理异常或错误。
  • 错误处理:如果某个操作失败,catch_error() 可以捕获错误并返回一个默认值或执行补救操作,从而避免程序崩溃。
  • 函数式编程风格expected<T, E> 提供了类似于 OptionEither 类型的功能,支持将操作的结果与错误分开处理,从而使代码更加健壮和清晰。
  • 类型安全expected<T, E> 通过将成功值和错误值明确区分,避免了直接使用 nullnullptr,增加了类型安全性。

4. expected<T, E>std::future<T> 的区别

  • std::future<T> 主要用于异步操作,通常表示任务的最终结果,但它不能直接处理错误信息,错误需要通过 std::exception 或类似方式来捕获。
  • expected<T, E> 不仅用于表示结果,还能明确地表示错误(例如,使用 E 类型)。它提供了丰富的错误处理方法(如 catch_error()),可以在处理链式操作时轻松地捕获和处理错误。

5. 总结

expected<T, E> 提供了一种函数式编程风格的方式来处理异步结果和错误。通过 map() 进行操作转换,使用 catch_error() 捕获并处理错误,这种方式让你在处理异步任务时既能获取结果,也能优雅地处理错误,而不需要显式地使用 try-catch 或检查错误码。

后面学不动了

http://www.xdnf.cn/news/964405.html

相关文章:

  • MinHook 对.NET底层的 SendMessage 拦截真实案例反思
  • PHP和Node.js哪个更爽?
  • 【论文阅读】多任务学习起源类论文《Multi-Task Feature Learning》
  • MyBatis注解开发的劣势与不足
  • LeetCode--27.移除元素
  • Leetcode 3578. Count Partitions With Max-Min Difference at Most K
  • HTML 列表、表格、表单
  • Docker-containerd-CRI-CRI-O-OCI-runc
  • 【kafka】Golang实现分布式Masscan任务调度系统
  • Python 自动化临时邮箱工具,轻松接收验证码,支持调用和交互模式(支持谷歌gmail/googlemail)
  • 【C++】26. 哈希扩展1—— 位图
  • 【PhysUnits】17.5 实现常量除法(div.rs)
  • Linux上并行打包压缩工具
  • Cryosparc: Local Motion Correction注意输出颗粒尺寸
  • 基于大模型的输尿管下段结石诊疗全流程预测与方案研究
  • 多场景 OkHttpClient 管理器 - Android 网络通信解决方案
  • 【AI study】ESMFold安装
  • Ribbon负载均衡实战指南:7种策略选择与生产避坑
  • 深度学习核心概念:优化器、模型可解释性与欠拟合
  • 【无标题新手学习期权从买入看涨期权开始】
  • OpenCV 图像像素值统计
  • Python入门手册:常用的Python标准库
  • C++初阶-list的模拟实现(难度较高)
  • C++学习-入门到精通【17】自定义的模板化数据结构
  • ParcelJS:零配置极速前端构建工具全解析
  • React 中的TypeScript开发范式
  • 存储设备应用指导
  • C++ 手写实现 unordered_map 和 unordered_set:深入解析与源码实战
  • 光伏功率预测 | BP神经网络多变量单步光伏功率预测(Matlab完整源码和数据)
  • word嵌入图片显示不全-error记