8

I am studying futures in my concurrent programming class. My professor has stated this in her slides:

"Valid" futures are future objects associated to a 
shared state, and are constructed by calling one of the following functions:

async
promise::get_future
packaged_task::get_future

future objects are only useful when they are valid. Default-constructed future objects are not valid (unless move-assigned a valid future).

I can't understand the meaning of the above, especially the "unless move-assigned a valid future" part. Could someone please explain this in simple terms, and perhaps show some example code as well?

Flame of udun
  • 2,136
  • 7
  • 35
  • 79
  • An oddity of `std::future` is that [*none* of its constructors or methods can make a future object valid](https://stackoverflow.com/questions/50020793/what-gives-a-stdfuture-some-shared-state). – Raedwald Apr 25 '18 at 11:22

1 Answers1

7

As stated in the std::future constructor:

Default-constructed future objects are not valid

This just means calling the default constructor for the object, something like:

std::future<int> f;

This will call constructor #1 which states:

Default constructor. Constructs a std::future with no shared state. After construction, valid() == false.

As for the other part:

(unless move-assigned a valid future)

What is meant here is that the move constructor (future( future&& other ) #2) will be called which states:

Move constructor. Constructs a std::future with the shared state of other using move semantics. After construction, other.valid() == false.

Basically, the state of other in this constructor is moved to this. That means if other.valid() == true then after the move constructor has returned other.valid() will be false and this.valid() will be true. If other.valid() was false to begin with then both will end up false.

std::future<int> fut; // fut.valid() == false, default constructor

std::future<int> valid_fut = std::async(std::launch::async, [](){ return 42; }); // obtain a valid std::future..
// valid_fut.valid() == true here

//now move valid_fut into new_fut
std::future<int> new_fut(std::move(valid_fut));
// new_fut.valid() == true
// valid_fut.valid() == false

To summarize:

  • Calling the default constructor for an std::future will result in valid() == false. Always.

  • Calling the move constructor for an std::future will result in valid() == true only if other.valid() was true before moving from it. False otherwise.

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
  • Small nitpick: the move constructor does not neccesarily come into play. What they literally mean is that if, at the end of your example, you were to do ```fut = std::move(new_fut);```, fut will be valid. That's the move-assignment they're talking about. (You actually also show this when assigning to valid_fut, though the compiler will try to treat that as a move constructor, not move assignment.) – Daniël van den Berg Apr 29 '21 at 10:24