• Advertisement
Sign in to follow this  

Some move semantic confusion

This topic is 1237 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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;
};

Share this post


Link to post
Share on other sites
Advertisement

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?

Share this post


Link to post
Share on other sites

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.

Edited by King Mir

Share this post


Link to post
Share on other sites
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. Edited by SmkViper

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
... 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.

Share this post


Link to post
Share on other sites

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)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement