C++ 异步编程全景:一次讲透 std::future详解

 更新时间:2026年11月04日 10:14:27   作者:吹什么轩  
本文系统讲解C++11引入的异步编程设施,聚焦std::future、std::shared_future、std::promise、std::packaged_task、std::async及std::launch等核心组件,感兴趣的朋友一起看看吧

一句话导读:把「子线程算完、把结果带回来」这件事,从 mutex + condition_variable 的样板代码,重构为一张「取货单」。图片里的 7 个组件,其实共用 1 个心智模型。

引子:一个「线程算完把结果带回来」的经典需求

假设你要在主线程里拿到子线程算出来的一个 int。在 C++11 之前,标准做法只有一条路:共享变量 + 互斥量 + 条件变量。

std::mutex mtx;
std::condition_variable cv;
bool ready = false;
int result = 0;
void worker() {
    std::lock_guard<std::mutex> lk(mtx);
    result = 42;              // 算出来的结果
    ready = true;
    cv.notify_one();
}
int main() {
    std::thread t(worker);
    std::unique_lock<std::mutex> lk(mtx);
    cv.wait(lk, [] { return ready; });
    std::cout << result << std::endl;   // 42
    lk.unlock();
    t.join();
}

这段代码本身没错,但它暴露了三个真实痛点,而这三点正是 <future> 头文件要解决的问题:

  1. 样板代码太多。一个线程传回一个 int,就要写一个 mutex、一个条件变量、一个 ready 标志位。
  2. 异常传不回来。子线程里抛出的异常无法跨越线程边界,std::terminate 是唯一结局,主线程永远不知道发生了什么。
  3. 语义不清晰。result 是个共享变量,谁都能改,读写时序完全靠人脑推理。

C++11 给出的答案是:把「结果」当成一件快递,做成一张单子。生产者负责发货,消费者拿着单子取货,两者之间不需要共享任何业务变量。

这套设施一共有 7 个名字,也就是本文要讲的 7 个组件:

#组件所属头文件
1std::future<future>
2std::shared_future<future>
3std::promise<future>
4std::packaged_task<future>
5std::async<future>
6std::launch<future>
7std::future_status<future>

它们在图片里的原始描述,先放在这里备查:

  1. std::future:用于获取异步操作的结果,只能获取一次
  2. std::shared_future:类似于 future,但可以被多次获取
  3. std::promise:用于存储值或异常,可以通过 future 获取
  4. std::packaged_task:包装可调用对象,便于异步获取结果
  5. std::async:便捷地启动异步任务
  6. std::launch:指定异步任务的启动策略
  7. std::future_status:表示 future 的状态

一、先建立心智模型:三个角色,七件工具

⚠️ 本篇最重要的一段话,请务必读完再往下看。

不要把这 7 个名字当成 7 个孤立的知识点死记。它们其实只分成三个角色,角色分清楚了,后面所有细节都是顺理成章的推论。

角色组件干什么
结果消费者std::future、std::shared_future手里拿「取货单」的那一方,负责取货
结果生产者std::promise、std::packaged_task、std::async负责把结果「写进」共享状态
控制与状态std::launch、std::future_status怎么启动任务、现在到底好没好

而串起这三个角色的,是一个看不见的东西:共享状态(shared state)。

一份共享状态里只会存三种东西之一:

  • 一个值
  • 一个异常
  • 「还没好」

future 和 promise 只是这份共享状态的两个端点:

std::promise<T>  ────►  [ 共享状态:值 / 异常 / 未就绪 ]  ────►  std::future<T>
   (发货端)                       (中间的中转仓)                      (取货端)

理解了这个模型,下面所有行为都可以自行推导出来:

  • 为什么 get() 会阻塞?因为中转仓里还空着,得等。
  • 为什么 get() 只能调用一次?因为取货端是「搬走」货物,搬完仓里就没了。
  • 为什么 shared_future 可以取多次?因为它取的是「复印件」,只看不搬。
  • 为什么生产者要 set_exception?因为中转仓除了值,还能存异常。

下面逐个拆解。

二、std::future:只发一次的取货单

std::future<T> 是整个体系的「交付物」,它持有一份共享状态。它的所有行为,都由「独占 + 一次性」这两条性质推出来:

2.1 四条核心性质

  1. 不可拷贝,只能移动(std::move)。取货单只有一张,交出所有权就没了。这也意味着 std::future 可以放进容器、可以作为返回值,但不能直接复制分发给多个线程。
  2. get() 阻塞直到就绪,然后「把货物搬走」。搬走了,单子就作废了 —— 调用后 valid() 立刻变为 false。
  3. 所以 get() 只能有效调用一次。第二次调用属于未定义行为,主流实现(libstdc++)会抛 std::future_error,错误码 std::future_errc::no_state。
  4. 异常会被「搬」成抛出。生产者写入的是异常时,消费者 get() 会在自己的线程里原样收到它 —— 这是它相对条件变量的最大优势。

2.2 最小可运行示例

#include <future>
#include <iostream>
#include <thread>
int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    std::thread t([&prom] {
        prom.set_value(42);        // 兑现承诺
    });
    int value = fut.get();         // 阻塞直到兑现,并且只能取一次
    std::cout << value << std::endl;
    t.join();
    return 0;
}

编译命令(顺手给你):

g++ -std=c++17 -pthread main.cpp -o main

2.3 验证「取一次就作废」

这段代码建议亲手跑一遍,印象会深得多:

std::future<int> f = std::async(std::launch::async, [] { return 7; });
std::cout << f.valid() << std::endl;   // 1
std::cout << f.get()   << std::endl;   // 7
std::cout << f.valid() << std::endl;   // 0:取货单已作废
// f.get();   // 未定义行为;libstdc++ 实际抛 std::future_error(no_state)

2.4 等待的四种姿势(重点)

很多人只知道 get(),结果一等等到天荒地老。其实 future 提供了四个接口:

接口是否阻塞返回值典型用途
get()阻塞到就绪T(结果)取结果,同时把异常抛出来
wait()阻塞到就绪void只要就绪,不要结果(不会抛异常)
wait_for()最多阻塞一段时间future_status带超时的等待、轮询进度
wait_until()最多阻塞到某个时间点future_status截止时间型任务

⚠️ 一个高频误解:wait() 只等不取,异常不会在这里抛出。如果你想让异常正常传播,必须调用 get()。

2.5 C++20 的一个小提醒

std::future 不是 awaiter,co_await fut 在标准库里编译不过。想做协程异步,需要自己写 operator co_await 或者用第三方库(如 cppcoro / folly)。这一点常有人误解,提前说清楚。

三、std::shared_future:取货单的复印件

std::future 的问题是「独占」—— 一个结果只能给一个人。如果三个线程都要等同一个结果呢?

这时候用 std::shared_future<T>。它和 future 的区别只有两点,但极其关键:

  1. 可拷贝、可赋值,多个副本共享同一份共享状态,可以分发给任意多个线程。
  2. get() 可以调用任意多次,返回类型是 const T&(而不是把值搬走的 T)。正因为只是「看」而不是「搬走」,所以能反复取。

3.1 从 future 转换:必须用 share()

std::future<int> f = std::async(std::launch::async, [] { return 42; });
std::shared_future<int> sf = f.share();   // f 立刻失效,sf 可以随便拷贝
auto reader = [sf](const char* who) {
    // get() 返回 const int&,多线程并发调用是安全的
    std::cout << who << " -> " << sf.get() << std::endl;
};
std::thread a(reader, "A");
std::thread b(reader, "B");
a.join();
b.join();

3.2 两个必须知道的细节

  1. 不能「拷贝 future 变成 shared_future」。std::future 本身不可拷贝,编译器直接报错。唯一正确的路径是 share()(或移动构造 std::shared_future<T> sf(std::move(f));)。
  2. share() 之后原 future 的 valid() 立刻变成 false,别再碰它,否则就是 no_state。

3.3 返回值类型的坑

shared_future<T>::get() 返回 const T&,是引用。所以:

const int& r = sf.get();   // 生命周期与 sf 绑定,不要让它活得比 sf 长

对于 shared_future<void>,get() 返回 void;对于 shared_future<T&>,返回 T&。

四、std::promise:手动兑现的承诺

std::promise 是生产端最底层的组件。语义非常朴素:我承诺将来给一个值,你现在先拿走一张取货单(get_future()),到时候我来兑现。

4.1 六条使用要点

  1. get_future() 对同一个 promise 只能调用一次,第二次抛 std::future_error(future_already_retrieved)。因为一个承诺只对应一张单子。
  2. set_value() 只能成功调用一次,重复调用抛 std::future_error(promise_already_satisfied)。
  3. set_exception() 把异常写进共享状态,消费者 get() 时在自己的线程里收到它 —— 这就是「跨线程抛异常」的标准做法。
  4. set_value_at_thread_exit() / set_exception_at_thread_exit():在线程真正退出时才兑现,常用于让线程局部对象(thread_local 变量)先安全析构,是模仿 std::async 语义的细节工具。
  5. promise 不可拷贝、可移动,所以它非常适合 std::move 进 lambda 或线程里。

4.2 把异常也一起快递过去

std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t([&prom] {
    try {
        throw std::runtime_error("boom");
    } catch (...) {
        prom.set_exception(std::current_exception());   // 异常也一起快递过去
    }
});
try {
    fut.get();                 // 结果没回来,异常先回来了
} catch (const std::exception& e) {
    std::cout << "caught: " << e.what() << std::endl;
}
t.join();

注意 std::current_exception() 这个细节:它捕获的是当前正在处理的异常,在 catch (...) 块里调用是唯一正确的位置。

4.3 最经典的坑:broken promise

如果 promise 还没兑现就被析构了会怎样?

标准规定:共享状态被置为「破碎」,与之关联的 future 立刻就绪,get() 时抛出 std::future_error,错误码是 std::future_errc::broken_promise。

std::future<int> f;
{
    std::promise<int> p;
    f = p.get_future();
}   // p 没兑现就析构了
try {
    f.get();   // 抛 std::future_error
               // code() == std::future_errc::broken_promise
} catch (const std::future_error& e) {
    std::cout << e.what() << std::endl;
}

这个设计非常友好:它让你不会永远卡死在一个永远不会来的结果上。这是「异常也走快递」这个设计的额外红利。

4.4 完整的 std::future_errc 枚举

错误码触发条件
broken_promisepromise 未兑现就被销毁
future_already_retrieved重复调用 get_future()
promise_already_satisfied重复调用 set_value()
no_state对无效(已作废)的 future 调用 get()/wait()

std::future_error 继承自 std::logic_error,所以用 catch (const std::logic_error&) 也能接住。

五、std::packaged_task:自带取货单的「可执行体」

promise 的问题是:你得自己写 try/catch,手动把结果塞进去,很啰嗦。

std::packaged_task 把这件事自动化了:你把一个可调用对象交给它,它帮你执行、帮你捕获返回值、帮你捕获异常,执行完自动写进共享状态。

5.1 关键点:模板参数是「函数签名」

std::packaged_task<int(int, int)> task([](int a, int b) { return a + b; });
//                 ^^^^^^^^^^^^ 这里是签名,不是 int

这是新手最容易写错的地方 —— 它和 std::function 的用法一致,参数是完整的函数类型。

5.2 基本用法

std::packaged_task<int(int, int)> task([](int a, int b) { return a + b; });
std::future<int> fut = task.get_future();
std::thread t(std::move(task), 1, 2);   // task 只可 move,交给线程去执行
std::cout << fut.get() << std::endl;    // 3
t.join();

注意:task 本身是可调用对象,task(1, 2) 就能在当前线程直接执行。把它交给线程只是「换个地方执行」而已。

5.3 为什么它比 std::function 更适合线程池?

std::function 只能执行,执行完结果就丢了;packaged_task 执行完,结果会稳稳落到那张取货单里。

所以它天生适合放进任务队列 / 线程池:队列里存的是 packaged_task(已擦除类型),调用方拿到的是 future。

一个极简线程池骨架(这段看懂,你就彻底懂它了):

class ThreadPool {
public:
    explicit ThreadPool(size_t n) {
        for (size_t i = 0; i < n; ++i) {
            workers_.emplace_back([this] {
                for (;;) {
                    std::packaged_task<void()> job;
                    {
                        std::unique_lock<std::mutex> lk(mtx_);
                        cv_.wait(lk, [this] { return stop_ || !tasks_.empty(); });
                        if (stop_ && tasks_.empty()) return;
                        job = std::move(tasks_.front());
                        tasks_.pop();
                    }
                    job();   // 执行,结果自动写进对应 future
                }
            });
        }
    }
    template <class F>
    std::future<std::invoke_result_t<F>> submit(F&& f) {
        using R = std::invoke_result_t<F>;
        std::packaged_task<R()> task(std::forward<F>(f));
        std::future<R> fut = task.get_future();
        {
            std::lock_guard<std::mutex> lk(mtx_);
            tasks_.emplace(std::move(task));   // 类型擦除成 packaged_task<void()>
        }
        cv_.notify_one();
        return fut;
    }
private:
    std::vector<std::thread> workers_;
    std::queue<std::packaged_task<void()>> tasks_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool stop_ = false;
};

小提示:std::packaged_task<void()> 可以接收任意签名的 packaged_task<R(Args...)>(通过移动构造的模板重载),这就是「类型擦除」的关键。实际工程里更常用 std::function<void()> 包一层,或者上 C++23 的 std::move_only_function。

5.4 reset():可以重复使用

reset() 会丢弃旧的共享状态,创建一个全新的。很多人不知道这个接口:

std::packaged_task<int()> task([] { return 1; });
task();                                              // 直接当函数调用:立刻执行
std::cout << task.get_future().get() << std::endl;   // 1
task.reset();                                        // 复位,拿到新的共享状态
task();
std::cout << task.get_future().get() << std::endl;   // 1

5.5 调用 operator() 时抛出的异常不会外泄

packaged_task 执行内部逻辑时抛出的异常,会被它捕获并存入共享状态,而不是向调用者抛。所以你在线程里调用 task() 是安全的,异常会在 future.get() 处现身。

六、std::async 与 std::launch:最省事,也最容易踩坑

std::async 是三个生产工具的「一站式封装」:既帮你启动任务,又直接把 future 递给你。

auto fut = std::async(std::launch::async, [] { return heavy(); });

但它有一个必须先讲清楚的参数:启动策略 std::launch。策略选错,你写的「并发」可能一行都没并发。

6.1 三种策略

// 1) 强制异步:一定另起线程(或交给实现内部的线程池)
auto f1 = std::async(std::launch::async, [] { return heavy(); });
// 2) 延迟执行:一个线程都不开,等你取结果时才在「当前线程」跑
auto f2 = std::async(std::launch::deferred, [] { return heavy(); });
f2.get();   // 此刻才真正执行
// 3) 默认(async | deferred):实现自己挑,可能并发也可能不并发
auto f3 = std::async([] { return heavy(); });
策略含义风险
launch::async必须新开线程(或线程池)执行线程数不可控,注意别开爆
launch::deferred完全惰性,get()/wait() 时才在调用线程同步执行不 get() 就永远不执行
async | deferred(默认)实现自由选择,甚至可以每次调用都换行为不可预期,最坑

6.2 四个必须记住的坑

  1. 默认策略是 async | deferred。标准允许实现在两者间任意选择。也就是说,不写策略的 std::async,并发行为是不可预期的。
  2. deferred 的任务不是「稍后自动执行」,而是「你不 get/wait 它,它永远不会执行」。这是「任务静默丢失」类 bug 的最大根源。
  3. deferred 的执行发生在调用 get()/wait() 的那个线程上,是同步执行。在 UI 线程 get() 一个 deferred 任务,界面会直接卡死。
  4. std::async 返回的 future 一旦析构,会阻塞等待任务完成(等价于隐式 join)。它不会「后台跑着不管」,也不是 detach。

6.3 第一招:用 wait_for(0) 判断任务到底有没有真跑起来

这是工程师必备的一个小技巧:

auto f = std::async([] { return work(); });
// 返回 deferred,说明被延迟了:连线程都没起,任务根本没在跑
if (f.wait_for(std::chrono::seconds(0)) == std::future_status::deferred) {
    // 在这里决定:是就地同步执行,还是报错,还是换一种方式
}

wait_for 传入 0 时长是合法的。只有真正在跑的任务才可能返回 timeout,被延迟的任务会立刻返回 deferred。

6.4 第二招:别让 future 在循环里「就地析构」

这是全网出现频率最高的 std::async 事故,没有之一:

// ❌ 反面教材:看着像「发出去就不管」,其实一轮都没并行
for (int i = 0; i < 3; ++i) {
    std::async(std::launch::async, [] { sleep_for_1s(); });
}   // future 就地析构 -> 阻塞等待 -> 总耗时约 3 秒
// ✅ 正确姿势:把取货单先收起来,最后统一取
std::vector<std::future<void>> fs;
for (int i = 0; i < 3; ++i) {
    fs.push_back(std::async(std::launch::async, [] { sleep_for_1s(); }));
}
for (auto& f : fs) f.get();   // 总耗时约 1 秒

记住这句话:std::async 返回的 future,是任务生命周期的「缰绳」。缰绳一松手(析构),程序就会站在原地等它跑完。 想真正并行,就把所有缰绳攥在容器里。

6.5 并行 map:最实用的一个模式

template <class F>
auto parallel_map(const std::vector<int>& input, F f) {
    std::vector<std::future<decltype(f(input[0]))>> futures;
    futures.reserve(input.size());
    for (int x : input) {
        futures.push_back(
            std::async(std::launch::async, [x, f] { return f(x); })
        );
    }
    std::vector<decltype(f(input[0]))> out;
    out.reserve(futures.size());
    for (auto& fut : futures) {
        out.push_back(fut.get());   // 逐个取货,异常也会在这里冒出来
    }
    return out;
}

注意:这是无限制并发版本,input 有一万个元素就会开一万个线程。生产代码请配合线程池 + 信号量限流。

七、std::future_status:三个取值,说清三种处境

wait_for() 和 wait_until() 不返回结果,而是返回一个 std::future_status。它是唯一能让你「不等死」的工具。

取值含义你该做什么
ready共享状态已就绪立刻 get(),不会阻塞(也可能立刻抛异常)
timeout还没好,时间到了任务可能仍在别的线程跑着,选择继续等或放弃
deferred任务被延迟了,根本没在跑这不是超时,需要显式 get()/wait() 触发执行

⚠️ 重点区分:timeout 是「在跑但没跑完」,deferred 是「压根没启动」。这两个语义完全不同,很多 bug 就是把它们混为一谈。

7.1 轮询等待(不阻塞 UI / 主循环)

auto f = std::async(std::launch::async, [] { return slow_work(); });
while (f.wait_for(std::chrono::milliseconds(100)) != std::future_status::ready) {
    // 没就绪就先干别的:刷进度、处理别的队列……
}
int value = f.get();   // 此时已就绪,立刻返回

7.2 带超时的「放弃式」等待

auto f = std::async(std::launch::async, [] { return slow_work(); });

if (f.wait_for(std::chrono::seconds(2)) == std::future_status::ready) {
    auto v = f.get();
} else {
    // 超时:这个任务还在跑
    // 注意:std::future 没有 cancel 接口,只能丢弃结果(任务会继续跑到结束)
}

⚠️ 这里有个残酷事实:标准库的 future 没有取消机制。想取消,只能自己再传一个 std::atomic<bool> 或者 std::stop_token(C++20)。

八、横向对比:一张表完成选型

组件可拷贝get() 次数谁写结果典型场景
std::future否,只可 move1 次生产者方线程结果、async 返回值
std::shared_future是任意次生产者方一份结果多方消费、重复读取
std::promise否,只可 move不适用(生产端)自己 set_value/set_exception回调里回传数据、跨层传值
std::packaged_task否,只可 move不适用(生产端)执行时自动写入线程池、任务队列
std::async否(返回 future)1 次由实现代为写入一行代码启动并发任务

选型口诀:

  • 只是想起个任务拿结果 → std::async(务必显式写 launch::async)
  • 拿到结果后要给多个消费者 → future.share() 变 shared_future
  • 结果来自别人的回调 / 事件(不是你能控制的可调用对象)→ std::promise
  • 任务要排队、复用、进线程池 → std::packaged_task
  • 要判断状态 / 带超时 → 配合 std::future_status

九、组合关系:其实只有一条主线

把这 7 个名字连起来看,底下只有一条主线:

                    ┌──────────────────────────────┐
std::promise   ────►│                              │
std::packaged_task ─┼──► [ 共享状态:值/异常/未就绪 ] ──► std::future ──► std::shared_future
std::async     ────►│                              │
                    └──────────────────────────────┘
                                    ▲
                    std::launch ────┘(决定怎么启动)
                      std::future_status(决定怎么等)
  • std::promise ←→ std::future:最原始的一对,手动写、手动取。
  • std::packaged_task:内部自带一个 promise,执行完替你 set_value / set_exception。
  • std::async:内部相当于 std::thread + std::packaged_task + future,再包上启动策略。
  • std::shared_future:把 future 那端的「独占」换成「共享」,生产端完全不用改。
  • std::launch 决定 async 怎么起线程;std::future_status 决定你怎么等。

真正的知识内核只有一个:共享状态(shared state) + 一次性写入 + 一次性(或多次)读取。 把这句话吃透,7 个组件的 API 都只是它的不同外衣。

十、踩坑清单:这 8 条建议背下来

  1. 对同一个 future 调用两次 get():未定义行为,libstdc++ 抛 std::future_error(no_state)。取之前先用 valid() 判断。
  2. promise 忘了 set_value 就析构:关联的 future 会收到 broken_promise 错误,而不是永远阻塞 —— 但仍然是个逻辑 bug。
  3. std::async 不指定策略:任务可能被延迟到 get() 时才同步执行,导致「假并发」,线程数也和预期不符。
  4. std::async 的 future 就地析构:会阻塞等待,循环里这么写等于串行。正确做法是收集到容器里统一 get()。
  5. 误以为 wait() 会抛异常:wait() 只等不取,异常只在 get() 时才被重新抛出。
  6. deferred 任务从不 get()/wait():它永远不会执行。注意区别 —— 只有 async 任务才会在 future 析构时被等待。
  7. 想拷贝 future 分发给多个线程:编译不过。先 share() 成 shared_future 再分发。
  8. 把 get() 放在持锁区里:get() 是阻塞的,持锁阻塞等于把所有等待者一起拖死。取结果之前先把锁放掉。

十一、回到开头:用这套设施重写那段代码

现在再回头看引子里那段 mutex + condition_variable 的样板代码。用 promise/future 重写,是这样的:

#include <future>
#include <iostream>
#include <thread>
int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    std::thread t([&prom] { prom.set_value(42); });
    std::cout << fut.get() << std::endl;   // 42
    t.join();
    return 0;
}

对比一下:

维度mutex + 条件变量promise + future
代码行数约 15 行约 6 行
共享变量有(result、ready)无(业务变量零共享)
异常传播❌ 做不到,直接 terminate✅ set_exception + get()
时序正确性靠人脑推理由库保证
超时等待需要另写 wait_for 循环wait_for 直接返回 future_status

这就是 <future> 存在的意义:它把「跨线程传结果」这件事,从一个需要小心推理的并发问题,降维成了一个简单的「发货 / 取货」流程问题。

十二、延伸:C++20 之后怎么办

标准库的这套设施已经相当完整,但有几个历史遗留缺口,值得知道:

  1. 没有取消机制。std::future 无法 cancel。C++20 引入了 std::stop_token / std::stop_source,配合 std::jthread 可以做协作式取消,但它不是 future 的一部分,需要你自己把 token 传进任务里。
  2. 没有 then / 回调链。标准库至今(C++23)没有提供 future.then()。想要链式异步,要么自己包一层,要么用第三方库(folly::Future、cppcoro)。
  3. 协程不能直接 co_await std::future。C++20 协程需要 awaiter,标准 future 没有实现。想用需要自己写适配器。
  4. std::async 的线程管理是「黑盒」。你无法控制它用多少线程、是否复用。要精细控制,请自己写线程池(见 5.3)或直接用 std::thread + packaged_task。

一句话总结现状:std::future 家族适合「一次性、拿到结果就完事」的场景;需要复杂的异步编排,就得上协程或第三方 Future 库。

十三、总结

最后把整篇串成一句话收尾:

std::future 是一张取货单,std::shared_future 是它的复印件,std::promise 是手动发货,std::packaged_task 是自动发货机,std::async 是下单即发货的快递柜,std::launch 决定发货时机,std::future_status 告诉你货到了没有。

七个名字,一个心智模型,剩下的全是细节。

如果你只能记住三件事,请记住这三件:

  1. future 只能 get() 一次,要多次取就先 share()。
  2. std::async 一定要显式写 std::launch::async,并且别让返回的 future 就地析构。
  3. promise 是唯一能「从别人的回调里把结果塞回来」的工具,也是唯一能优雅传异常的工具。

参考与延伸阅读

如果这篇对你有帮助,欢迎点赞、收藏、关注。评论区聊聊:你在项目里被 std::async 的默认策略坑过吗?

到此这篇关于C++ 异步编程全景:一次讲透 std::future详解的文章就介绍到这了,更多相关C++ 异步编程std::future详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++ 将数据转为字符串的几种方法

    C++ 将数据转为字符串的几种方法

    这篇文章主要介绍了C++ 将数据转为字符串的几种方法,十分的实用,有需要的小伙伴可以参考下。
    2015-06-06
  • C++实现模拟shell命令行(代码解析)

    C++实现模拟shell命令行(代码解析)

    这篇文章主要介绍了C++实现模拟shell命令行,本文通过实例代码进行命令行解析,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • C/C++自定义类型结构体全解析

    C/C++自定义类型结构体全解析

    c语言有内置类型(char short int long flaot double long double),也有自定义类型—结构体(struct) 枚举(enum) 联合体(union) 本文介绍结构体,感兴趣的朋友跟随小编一起看看吧
    2025-05-05
  • 在c和c++中实现函数回调

    在c和c++中实现函数回调

    如何在c和c++中实现函数回调呢?现在小编就和大家分享一下在c/c++中实现函数回调的示例代码,需要的朋友可以参考下
    2013-07-07
  • 详解C/C++如何获取路径下所有文件及其子目录的文件名

    详解C/C++如何获取路径下所有文件及其子目录的文件名

    这篇文章主要为大家详细介绍了在C/C++中如何获取路径下所有文件及其子目录的文件名,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2023-03-03
  • C++虚继承的实现原理由内存布局开始讲起

    C++虚继承的实现原理由内存布局开始讲起

    为了解决多继承时的命名冲突和冗余数据问题,C++提出了虚继承,使得在派生类中只保留一份间接基类的成员,下面我们从内存布局看看虚继承的实现原理
    2022-06-06
  • C语言结构体数组的定义和使用详解

    C语言结构体数组的定义和使用详解

    结构体中也有数组,称为结构体数组。它与数值型数组几乎是一模一样的,只不过需要注意的是,结构体数组的每一个元素都是一个结构体类型的变量,都包含结构体中所有的成员项。本文将带大家详解了解结构体数组的定义与使用
    2021-12-12
  • C语言实现扫雷游戏详细代码实例

    C语言实现扫雷游戏详细代码实例

    这篇文章主要介绍了C语言实现扫雷游戏详细代码实例,有感兴趣的同学可以借鉴参考下
    2021-02-02
  • C语言学生学籍管理系统课程设计

    C语言学生学籍管理系统课程设计

    这篇文章主要介绍了C语言学生学籍管理系统课程设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • C++类型转换详解

    C++类型转换详解

    类型转换有c风格的,当然还有c++风格的。c风格的转换的格式很简单(TYPE)EXPRESSION,但是c风格的类型转换有不少的缺点,有的时候用c风格的转换是不合适的,因为它可以在任意类型之间转换
    2021-10-10

最新评论