C++ Pitfall : shared_future

, in Computing

A shared future is a lovely thing to contemplate. A shared_future, on the other hand, is a slippery beast.

Promises and Futures are great ways of transferring ownership of objects between threads. A common use case is a task queue that allows you to submit tasks and wait on the returned future to get the results.

// submitTask creates a promise and returns the corrosponding future
auto future = taskQueue.submitTask(
    [](){
        return someLongRunningOperation();
    });
auto result = future.get();

But there are two types of futures - future and shared_future. What is the difference and which one should you use?

Both provide access to the (confusingly named) "shared state" that conceptually consists of a flag to say whether the result is available, the result, or possibly an exception. In the case of future the shared state can only be accessed once - calling get() provides access but effectively resets the shared state. shared_future allows the state to be accessed many times, even from different threads - useful if multiple threads need to wait on a result.

A consequence of this design is that futures cannot be copied, only moved. This can be annoying if you find the need to pass futures around or store them in containers. It is tempting (and I have seen code that does this) to just use shared_futures, which do not have this limitation.

But although shared_future acts like a drop-in replacement for future with the same interface, there is a subtle pitfall.

future::get() and shared_future::get() actually do different things.

future::get() effectively moves the result back to your code. shared_future::get() only provides you with a const reference for you to copy.

If performance is a concern (and why else would you be using threads?) then the cost of the copy for large results can be prohibitive. This subtle issue is not obvious when eyeballing the code since the two type of futures appear to behave identicallyIf you really want to have the convenience of shared_future, consider wrapping large objects in easy-to-copy shared_ptrs.

For this reason I have added std::shared_future to my list of C++ features that raise my hackles during code reviews. Unless multiple threads really need to wait on a result, a non-shared future is probably what you want.