Some move semantic confusion

Started by
7 comments, last by SmkViper 9 years, 6 months ago

Consider the following code:


struct Crap {};

struct Thing
{
    void addCrap()
    {
        m_Crap.push_back(std::move(Crap()));
    }
private:
    std::vector<Crap> m_Crap;
};

In my mind, after the push_back call, m_Crap is holding an invalid Crap object. Isn't Crap() temporarily allocated on the stack, moved into the vector, and then deallocated, resulting in m_Crap referencing a memory block on the stack?

Am I also correct to assume that the following is invalid as well?


struct Crap {};

struct Thing
{
    void addCrap()
    {
        Crap crap;
        m_Crap.push_back(std::move(crap));
    }
private:
    std::vector<Crap> m_Crap;
};
"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty
Advertisement
Moves are tricky. Think of them not as moving an object, but moving the guts of an object.

In your case the code does nothing significant because Crap has nothing internal to move (or copy, or destroy).



Moves are best talked about in terms of resources that are controlled by the moving objects, such as memory. If I have a Foo that holds a pointer to some memory, for instance, moving the Foo doesn't do anything "to" the Foo itself - it just moves the pointer from one Foo instance into the other.

Actually, even that is not strictly true, because your move constructor and move assignment operator are free to do anything they want. It's by convention, though, that moves take the internal state of one object and place into another.

At no time should you be left with an invalid object - maybe one that doesn't contain anything anymore, but not invalid.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

I'm still learning C++ myself so somebody correct me if I'm wrong, but my understanding is the move constructors (and move assignment operators) are for objects that manage memory or other resources. For example, if I had an object that held a gigabyte-sized array of data then the copy constructor would need to *copy* that huge chunk of RAM which is pretty wasteful if the argument is a temporary that's just going to be destroyed as soon as it goes out of scope. The move constructor knows that its argument is temporary so it can simply take over responsibility for freeing that chunk of RAM instead of copying it.

Put another way, it's not the object that's moving but rather it's the responsibility for the resources it contains that is being handed off from one object to another.

Do I have that right?

In addition to what others have said:

std::move is actually just a static_cast that converts an lvalue to an rvalue/rvalue reference.

It does nothing to something that's already an rvalue, like it's used in your first example.

The second example is a pretty typical use of std move. crap is a named variable and therefore an lvalue, but my using std::move, you're indicating that the variable is not intended to be used anymore except to destroy it, so it's ok to move the guts out of it. So std::move casts the type to an rvalue reference.

But because Crap doesn't have a non-trivial move constructor, using std::move has no effect anyway. Also note, stl containers will not call move constructors that aren't marked as noexcept, when doing so would prevent the strong exception guarantee.

I'm still learning C++ myself so somebody correct me if I'm wrong, but my understanding is the move constructors (and move assignment operators) are for objects that manage memory or other resources. For example, if I had an object that held a gigabyte-sized array of data then the copy constructor would need to *copy* that huge chunk of RAM which is pretty wasteful if the argument is a temporary that's just going to be destroyed as soon as it goes out of scope. The move constructor knows that its argument is temporary so it can simply take over responsibility for freeing that chunk of RAM instead of copying it.

Put another way, it's not the object that's moving but rather it's the responsibility for the resources it contains that is being handed off from one object to another.

Do I have that right?

Yeah, that's correct.

In addition to what the posters above have already said, in your second example, your "crap" variable has undefined state after the std::move, so you should never use it again (it's safe to destroy, but otherwise you have no guarantees).

Yes, for the pedantic, since it's an empty struct it never moves anything and therefore there are no problems, but I'm referring to cases where you don't know the internal contents of the object you're moving and it does have a valid move constructor/assignment operator.

In addition to what the posters above have already said, in your second example, your "crap" variable has undefined state after the std::move, so you should never use it again (it's safe to destroy, but otherwise you have no guarantees).


That is untrue. The object must be in an "unspecified but valid" state, not "undefined". You are for example allowed to call member function which have no specific preconditions. So calling std::vector::empty after a move is perfectly fine while std::vector::pop_back could or could not work. See also this discussion.

Also, the "unspecified but valid" state applies only to the standard library. Your own code or other libraries may have stronger guarantees.

... or weaker ones, for that matter. As I noted above, you're free to do any dumb things you wish inside a move constructor and/or move assignment operator.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

In addition to what the posters above have already said, in your second example, your "crap" variable has undefined state after the std::move, so you should never use it again (it's safe to destroy, but otherwise you have no guarantees).


That is untrue. The object must be in an "unspecified but valid" state, not "undefined". You are for example allowed to call member function which have no specific preconditions. So calling std::vector::empty after a move is perfectly fine while std::vector::pop_back could or could not work. See also this discussion.


Ok, yeah, 'undefined' might have been the wrong word to use.

My point simply was: You have no idea what state the object is in. Maybe the guts are gone and you've got an empty object. Maybe some stuff is still set but 'expensive' contents are gone. Maybe the move didn't actually happen and the object is untouched.

I simply wanted to point out that once you've called std::move on an object and passed it off you should probably not do anything else with it since you can't be sure it'll do what you want.

In my code or code I review, I personally treat re-use of a variable that std::move has been called on as if you tried to access a pointer after it's been deleted. (With the exception that it won't throw you into undefined behavior territory)

This topic is closed to new replies.

Advertisement