What How to Do With a Moved-From Object

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.

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 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).

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.

How can moved objects be used? [duplicate]

Yes, you can move another object into it. std::swap does this.

Moved objects are still destructed?

Yes, moved-from objects are destructed. They remain in an undetermined but valid state. They're still objects.

It's best if you recall that C++ doesn't actually move anything. std::move just gives you an rvalue. So-called "move constructors" are just convenient alternatives to copy constructors, found during lookup when you have an rvalue, and allowing you the opportunity to swap your class's encapsulated data rather than actually copying it. But C++ doesn't move anything for you, and it can't tell when you have done some moving.

As such, it would be infeasibly dangerous and impractical for C++ to have any kind of rule that somehow stopped "moved-from" objects, if we could even decide what this meant in general, from later undergoing destruction. Make this destruction safe (a no-op, ideally) for your moved-from objects (e.g. by setting source pointers to nullptr in your move constructor) and you'll be fine.

What does explicitly-defaulted move constructor do?

What does explicitly-defaulted move constructor do?

It will initialize its member variables with the corresponding member variables in the moved from object after turning them into xvalues. This is the same as if you use std::move in your own implementation:

Base(Base&& other) noexcept : id(std::move(other.id)) {}

Note though that for fundamental types, like int, this is no different from copying the value. If you want the moved from object to have its id set to 0, use std::exchange:

Base(Base&& other) noexcept : id(std::exchange(other.id, 0)) {}

Base& operator=(Base&& other) noexcept {
if(this != &other) id = std::exchange(other.id, 0);
return *this;
}

With that, the Foo move constructor and move assignment operator can be defaulted and "do the right thing".


Suggestion: I think Base would benefit from a constructor that can initialize id directly. Example:

Base(int Id) : id(Id) {
std::cout << "Base() called " << id << std::endl;
}

Base() : Base(0) {} // delegate to the above

Now, the Foo constructor taking an id as an argument could look like this:

Foo(int Id) : Base(Id) {
std::cout << "Foo() called " << id << std::endl;
}

c++ reassign after moved

This is guaranteed. After the move, the vector is guaranteed to be empty, i.e. has no elements. It's fine to be assigned by other contents.

After the move, other is guaranteed to be empty().



Related Topics



Leave a reply



Submit