4

Is it safe (thread-safe) to declare a std::future as being mutable since its get() function changes its state. I assume that it's like std::mutex which is safe to be made mutable.

template <typename T>
struct S {
    void query() {
        m_fut = doAsyncQuery();
    }

    template <typename L>
    void get(L lambda) const {
        lambda(m_f.get());
    }

    mutable std::future<T> m_f;
};
James
  • 9,064
  • 3
  • 31
  • 49
  • AFAIK `std::future` can't work as const object – NathanOliver Sep 23 '19 at 21:53
  • Oh... you mean the *keyword* `mutable`. I reread the question at least 6 times before I got that, then I remembered the `mutable std::mutex` guideline from Effective Modern C++. But you speak about it being *safe*. Why wouldn't it? What concerns do you have in mind? – Fureeish Sep 23 '19 at 22:19
  • 1
    What do you mean by "safe"? Do you mean thread-safe? Then no, std::future is not thread-safe. However, as NathanOliver said, std::future can't be const (as by definition its state changes when the attached operation is finished), so you have no choice but to make it mutable. You just need to make sure to make it thread-safe yourself if necessary. – Max Vollmer Sep 23 '19 at 22:20
  • @Fureeish My concern is that I've not seen it explictly stated anywhere that it's ok. In our use-case we use a future to return the results of a database read. I'm wondering why our code to extract the results from the future needs to be non-const since all we do is pass the results to a lambda. – James Sep 23 '19 at 22:21
  • @MaxVollmer Interesting, thanks. The cppreference example for std::future::get doesn't use any explicit synchronisation when extracting the value: https://en.cppreference.com/w/cpp/thread/future/get – James Sep 23 '19 at 22:32
  • It doesn't have to, it's all happening in the same thread. std::future is only intended for use in a single thread, it makes no sense to share it between multiple threads. Thread-safety doesn't only mean synchronization, it also means making sure resources that shouldn't be accessed by more than one thread can only be accessed by one thread. – Max Vollmer Sep 23 '19 at 22:33
  • @MaxVollmer How? The value that'll be extracted from the future is populated by a different thread. – James Sep 23 '19 at 22:35
  • Yes, but the future is created and used by only one thread. The thread that runs the attached operation has nothing to do with this. `std::future` is not thread-safe regarding the `get`, `valid` and `wait` methods. These must only be accessed by one thread, usually the same thread that created the `std::future` object in the first place. – Max Vollmer Sep 23 '19 at 22:35

1 Answers1

2

is it thread-safe?

No, but that doesn't matter.

std::future is intended for use in a single thread and is not thread-safe by design. It makes no sense to share it between multiple threads. You are supposed to call get() only once and then throw it away.

(The thread that runs the attached operation and sets the value returned by get() is not relevant here. The implementation manages and hides the necessary synchronization for you.)

(Of course you can transfer ownership of the std::future from one thread to another. But at any given time only one thread should hold and use the std::future.)

can it be mutable?

Yes. In fact, std::future cannot be const since its state changes by definition when the attached operation completes and when you call get().

However mutable should not be the keyword to look for here. Of course you can wrap a std::future in a class or struct with the mutable keyword and then create a const instance of that class, but you are hiding away an important piece of information (namely that your wrapper is not const at all). (It might make sense in a more complex class, but I would argue that a complex class with an std::future member is a sign of bad design.)

Instead you should make such a wrapper class or struct non-const as well:

template <typename T>
struct S {
    void query() {
        m_fut = doAsyncQuery();
    }

    template <typename L>
    void get(L lambda) {    // remove const here
        lambda(m_f.get());
    }

    std::future<T> m_f;     // remove mutable here
};

As long as you are aware that instances of S must not be const, that you can call get only once, and that you shouldn't share an S between threads, you should be safe.

Max Vollmer
  • 8,412
  • 9
  • 28
  • 43