C++
std::thread
return value
multithreading
programming

C Simple return value from stdthread?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

std::thread does not give you a direct return value the way a normal function call does. A thread runs independently, so you need an explicit channel for sending the result back to the calling code.

Use std::promise and std::future

The standard library way to hand one result from a worker thread back to the caller is std::promise paired with std::future. The worker sets the value, and the main thread waits for it.

cpp
1#include <future>
2#include <iostream>
3#include <thread>
4
5void compute_square(int x, std::promise<int> promise) {
6    promise.set_value(x * x);
7}
8
9int main() {
10    std::promise<int> promise;
11    std::future<int> future = promise.get_future();
12
13    std::thread worker(compute_square, 12, std::move(promise));
14
15    int result = future.get();
16    worker.join();
17
18    std::cout << "result: " << result << '\n';
19}

This pattern is explicit and safe. future.get() blocks until the result is ready, and it can only be consumed once.

Propagate Exceptions Cleanly

One reason promise and future are useful is that they can carry exceptions as well as values. That keeps error handling structured.

cpp
1#include <exception>
2#include <future>
3#include <iostream>
4#include <stdexcept>
5#include <thread>
6
7void risky_work(std::promise<int> promise) {
8    try {
9        throw std::runtime_error("work failed");
10    } catch (...) {
11        promise.set_exception(std::current_exception());
12    }
13}
14
15int main() {
16    std::promise<int> promise;
17    std::future<int> future = promise.get_future();
18
19    std::thread worker(risky_work, std::move(promise));
20
21    try {
22        std::cout << future.get() << '\n';
23    } catch (const std::exception& ex) {
24        std::cout << ex.what() << '\n';
25    }
26
27    worker.join();
28}

If the worker fails, the caller sees the failure when it calls get(). That is much cleaner than juggling shared error flags and mutexes.

Consider std::async When You Only Need a Result

If you do not specifically need a raw std::thread, std::async is often the simplest answer. It already returns a std::future.

cpp
1#include <future>
2#include <iostream>
3
4int compute_sum(int a, int b) {
5    return a + b;
6}
7
8int main() {
9    std::future<int> future = std::async(std::launch::async, compute_sum, 20, 22);
10    std::cout << future.get() << '\n';
11}

This is usually easier than wiring a promise manually. The tradeoff is that std::async gives you less explicit control over thread management.

Shared State Is Possible but Easier to Get Wrong

You can also write the result into shared memory and protect it with a mutex:

cpp
1#include <iostream>
2#include <mutex>
3#include <thread>
4
5int main() {
6    int result = 0;
7    std::mutex mutex;
8
9    std::thread worker([&]() {
10        int local = 7 * 9;
11        std::lock_guard<std::mutex> lock(mutex);
12        result = local;
13    });
14
15    worker.join();
16
17    std::lock_guard<std::mutex> lock(mutex);
18    std::cout << result << '\n';
19}

This works, but it scales poorly once you need failure handling, multiple results, or more complex lifetime rules. Futures communicate intent better.

Common Pitfalls

The most common mistake is expecting std::thread itself to store the function's return value. It does not. The thread object only represents execution and joinability.

Another common error is forgetting to call join() or detach(). Destroying a joinable thread causes std::terminate, which can look unrelated to the result-passing logic.

Be careful with references as well. If the worker writes into shared state by reference, that state must stay alive until the thread is done. Otherwise you have a data race or a dangling reference bug.

Finally, if you only want asynchronous computation plus a return value, do not force std::thread into the design. std::async may be the simpler tool.

Summary

  • 'std::thread has no built-in return value channel.'
  • Use std::promise and std::future when a worker thread must return one result.
  • Futures can also propagate exceptions back to the caller.
  • Use std::async when you want a result without managing the thread manually.
  • Shared variables work, but they need careful synchronization and are easier to misuse.

Course illustration
Course illustration

All Rights Reserved.