What Constitutes a Valid State For a "Moved From" Object in C++11

What constitutes a valid state for a moved from object in C++11?

You define and document for your types what a 'valid' state is and what operation can be performed on moved-from objects of your types.

Moving an object of a standard library type puts the object into an unspecified state, which can be queried as normal to determine valid operations.

17.6.5.15 Moved-from state of library types                                        
[lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from
(12.8). Move operations may be explicitly specified or implicitly
generated. Unless otherwise specified, such moved-from objects shall
be placed in a valid but unspecified state.

The object being in a 'valid' state means that all the requirements the standard specifies for the type still hold true. That means you can use any operation on a moved-from, standard library type for which the preconditions hold true.

Normally the state of an object is known so you don't have to check if it meets the preconditions for each operation you want to perform. The only difference with moved-from objects is that you don't know the state, so you do have to check. For example, you should not pop_back() on a moved-from string until you have queried the state of the string to determine that the preconditions of pop_back() are met.

std::string s = "foo";
std::string t(std::move(s));
if (!s.empty()) // empty has no preconditions, so it's safe to call on moved-from objects
s.pop_back(); // after verifying that the preconditions are met, pop_back is safe to call on moved-from objects

The state is probably unspecified because it would be onerous to create a single useful set of requirements for all different implementations of the standard library.


Since you are responsible not only for the specification but also the implementation of your types, you can simply specify the state and obviate the need for querying. For example it would be perfectly reasonable to specify that moving from your pimpl type object causes do_stuff to become an invalid operation with undefined behavior (via dereferencing a null pointer). The language is designed such that moving only occurs either when it's not possible to do anything to the moved-from object, or when the user has very obviously and very explicitly indicated a move operation, so a user should never be surprised by a moved-from object.


Also note that the 'concepts' defined by the standard library do not make any allowances for moved-from objects. That means that in order to meet the requirements for any of the concepts defined by the standard library, moved-from objects of your types must still fulfill the concept requirements. This means that if objects of your type don't remain in a valid state (as defined by the relevant concept) then you cannot use it with the standard library (or the result is undefined behavior).

Does moving leave the object in a usable state?

From n3290, 17.6.5.15 Moved-from state of library types [lib.types.movedfrom]

  1. Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

Since the state is valid, this means you can safely operate on v2 (e.g. by assigning to it, which would put it back to a known state). Since it is unspecified however, it means you cannot for instance rely on any particular value for v2.empty() as long as it is in this state (but calling it won't crash the program).

Note that this axiom of move semantics ("Moved from objects are left in a valid but unspecified state") is something that all code should strive towards (most of the time), not just the Standard Library components. Much like the semantics of copy constructors should be making a copy, but are not enforced to.

Is it allowed to self-move an object in C++?

From cpp ref:

Also, the standard library functions called with xvalue arguments may assume the argument is the only reference to the object; if it was constructed from an lvalue with std::move, no aliasing checks are made. However, self-move-assignment of standard library types is guaranteed to place the object in a valid (but usually unspecified) state:

std::vector<int> v = {2, 3, 3};
v = std::move(v); // the value of v is unspecified

What is a state of an object after it was moved-from via a reference to its base class?

The standard does not make any such requirement on move constructors for C++ classes that are not a part of the standard library. The library writer is free to do what they want. For components that are a part of the standard library, the C++17 standard does say something (emphasis mine)

§ 20.5.5.15 Moved-from state of library types [lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from (15.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

For classes in the standard library that are a part of an inheritance hierarchy, you would have to consult the documentation to see what happens when you try and move construct a base object from a base class rvalue reference a derived class object. So if you could specify which class in particular you were worried about I could try and help more.


Not sure how relevant this is to your question, but regarding what would really happen in such a constructor. Remember that the virtual function mechanism is disabled in constructors for the object that is going to be constructed, but not for the referenced object. In the following example

class Base {
public:
virtual vector<int> extract_vector() { ... }

// Move constructor can use virtual functions on the other object
Base(Base&& other) {
this->vec = other.extract_vector();
}

private:
std::vector<int> vec;
};

class Foo : public Base {
public:
vector<int> extract_vector() override { ... }

private:
// this does something ¯\\_(ツ)_/¯
SpecialVectorAdaptor<std::vector<int>> vec;
};

The virtual function extract_vector on other in the move constructor for Base will be called, and you can utilize virtual methods on the other object. But you cannot use any such virtual methods on the class that is going to be constructed.


So long story short, the behavior of the code you have shown will depend strictly on the implementation of the classes in question.

What is technically a moved-from object?

Well, std::move(x); is simply casting to r-value reference - it doesn't do anything. But OK, assume you had this:

int x = 42;
int z = std::move(x);
int y = x; // is this ok?

Yes, it is OK and x, y, z will all be 42. Because for trivial types moving is the same as copying.

But what of other more complex classes?

In general, there are only a few strict requirements for moving and move-from objects.

  1. moved-from object needs be copy/move assignable and destructible.
  2. target should be "equal" to pre-moved source.

These requirements are necessary. A lot of STL functionality and other libraries will not operate properly with types that fail to support these.

In practice, simple POD types simply perform a copy on move while containers transfer resource ownership from source to target while destroying whatever resources target owned. Technically, data swap between the objects is also a legitimate action - and some believe it to be a good idea - I strongly disagree with it because if I wanted to swap data then I'd trigger std::swap instead of std::move.

Nevertheless, there is no strict requirement what move must do to moved-from objects - which is important when dealing with complex classes. For instance, one can write a custom allocator for std::vector that will explicitly forbid to move, in which case on move some sort of data copying mixed with data moving will take place and different versions of STL may or may not clear original object.

Notes: cppreference has explicitly stated requirements on moved-from objects for majority of classes in the documentation. So you'd know what exactly what to expect and it's not always intuitive. Usually they go for the laziest action possible.

What can I do with a moved-from object?

Moved-from objects exist in an unspecified, but valid, state. That suggests that whilst the object might not be capable of doing much anymore, all of its member functions should still exhibit defined behaviour — including operator= — and all its members in a defined state- and it still requires destruction. The Standard gives no specific definitions because it would be unique to each UDT, but you might be able to find specifications for Standard types. Some like containers are relatively obvious — they just move their contents around and an empty container is a well-defined valid state. Primitives don't modify the moved-from object.

Side note: I believe it's T c = std::move(a) so that if the move constructor (or copy constructor if no move is provided) is explicit the function will fail.

How to handle invalid state after move especially for objects with validating constructor?

You are trying to maintain two invariants at once, and their semantics are in conflict. The first invariant is the validity of the certificate. The second is for memory management.

For the first invariant, you decided that there can be no invalid constructed object, but for the second, you decided that the object can be either valid or unspecified. This is only possible because the deallocation has a check somewhere.

There is no way around this: you either add a check for the first or you decouple the invariants. One way of decoupling them is to follow the design of std::lock_guard

cert c = open_cert(); // c is guaranteed to not have memory leaks and is movable
{
cert_guard cg{c}; // cg is guaranteed to be valid, but cg is non-movable
}

But wait, you might ask, how do you transfer the validity to another cert_guard?

Well, you can't.

That is the semantics you chose for the first invariant: it is valid exactly during the lifetime of the object. That is the entire point.

† Unspecified and invalid as far as the certificate is concerned.



Related Topics



Leave a reply



Submit