Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does moving std::optional not reset state

I was rather surprised to learn that the move constructor (and assignment for that matter) of std::optional does not reset the optional moved from, as can be seen in [19.6.3.1/7] which states "bool(rhs) is unchanged."

This can also be seen by the following code:

#include <ios>
#include <iostream>
#include <optional>
#include <utility>

int main() {
  std::optional<int> foo{ 0 };
  std::optional<int> bar{ std::move(foo) };

  std::cout << std::boolalpha
            << foo.has_value() << '\n'  // true
            << bar.has_value() << '\n'; // true
}

This seems to contradict other instances of moving in the standard library such as with std::vector where the container moved from is usually reset in some way (in vector's case it is guaranteed to be empty afterwards) to "invalidate" it even if the objects contained within it themselves have been moved from already. Is there any reason for this or potential use case this decision is supposed to support such as perhaps trying to mimic the behavior of a non-optional version of the same type?

like image 818
Lemon Drop Avatar asked Aug 12 '18 01:08

Lemon Drop


People also ask

What happens when you to std :: move?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

Why is std :: move necessary?

std::move itself does "nothing" - it has zero side effects. It just signals to the compiler that the programmer doesn't care what happens to that object any more. i.e. it gives permission to other parts of the software to move from the object, but it doesn't require that it be moved.

Does STD optional allocate memory?

What's more, std::optional doesn't need to allocate any memory on the free store. std::optional is a part of C++ vocabulary types along with std::any , std::variant and std::string_view .

Does std :: move make a copy?

std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.


3 Answers

Unless otherwise specified, a moved-from object of class type is left in a valid but unspecified state. Not necessarily a "reset state", and definitely not "invalidated".

For primitive types , moving is the same as copying, i.e. the source is unchanged.

The defaulted move-constructor for a class type with primitive members will move each member, i.e. leave the primitive members unchanged; a user-defined move constructor might or might not "reset" them.

A moved-from vector may or may not still have elements in it. We would expect it not to, since that's efficient, but it cannot be relied on.

A moved-from std::string may still have elements in it, because of Small String Optimization.


move on std::optional is actually specified by the standard (C++17 [optional.ctor]/7). It is defined as doing move on the contained type, if present. It does not turn a valued optional into a valueless optional.

So it is actually expected that your code outputs true true, and the actual contained value in foo should stay the same too.


Regarding the question of why std::optional 's move-constructor is defined this way: I can't say for sure; but an optional is not like a vector with max size of 1. It's more like a variable with a validity flag tacked on. Accordingly, it makes sense for moving an optional to be like moving the variable.

If moving an optional left the old one "empty", then a = std::move(b); would invoke the destructor of b's managed object, which would be unexpected (to me, at least).

like image 75
M.M Avatar answered Oct 06 '22 14:10

M.M


In a word: Performance.

One of the chief motivating reasons for move semantics to exist in the first place is performance. So the special operations move construction and move assignment should be as fast as possible for all types.

In order to assist this goal, it is standard practice that moved-from objects be left in a valid but unspecified state. So the very minimum that optional move construction/assignment need to do is to move from the source argument. To specify setting the source to not have a value after the move is equivalent to saying:

After you move, do some extra, unnecessary work.

No matter how small that extra work is, it is non-zero. Some (and I dare say many) clients will not need that extra work, and should not have to pay for it. Clients who do need it can easily add x.reset() after the move, putting the moved-from optional into a well-specified state.

like image 30
Howard Hinnant Avatar answered Oct 06 '22 16:10

Howard Hinnant


What that paragraph says is that if that optional had a value, it still has a value. Since that value has been moved from (to the newly constructed object), it could be a different value than what it had before the move. This allows you to access the moved-from optional object the same way as a moved-from non-optional object, so the behavior of a T vs. optional<T> (when it contains an object) when accessed after the move is the same.

Also, the overall effect of a move from an optional depends on how the contained type T handles a move. Other classes (like vector) do not have this dependency.

like image 5
1201ProgramAlarm Avatar answered Oct 06 '22 14:10

1201ProgramAlarm