Reputation: 293
Can someone explain why the original object that is passed to a new object via std::move is still valid afterwards?
#include <iostream>
class Class
{
public:
explicit Class(const double& tt) : m_type(tt)
{
std::cout << "defaultish" << std::endl;
};
explicit Class(const Class& val) :
m_type(val.m_type)
{
std::cout << "copy" << std::endl;
};
explicit Class(Class&& val) :
m_type(val.m_type)
{
m_type = val.m_type;
std::cout << "move: " << m_type << std::endl;
};
void print()
{
std::cout << "print: " << m_type << std::endl;
}
void set(const double& tt)
{
m_type = tt;
}
private:
double m_type;
};
int main ()
{
Class cc(3.2);
Class c2(std::move(cc));
c2.print();
cc.set(4.0);
cc.print();
return 0;
}
It outputs the following:
defaultish
move: 3.2
print: 3.2
print: 4
I would expect the calls to cc.set() and cc.print() to fail...
UPDATE
Thanks to answers below, we've identified that 1) I wasn't moving anything in the move constructor, and 2) std::move()
on an int or double doesn't do anything because it's more expensive to move these types than to simply copy. The new code below updates the class's private member variable to be of type std::string instead of a double, and properly calls std::move when setting this private member variable in the Class' move constructor, resulting in an output that shows how a std::move results in a valid but unspecified state
#include <iostream>
#include <string>
class Class
{
public:
explicit Class(const std::string& tt) : m_type(tt)
{
std::cout << "defaultish" << std::endl;
};
explicit Class(const Class& val) :
m_type(val.m_type)
{
std::cout << "copy" << std::endl;
};
explicit Class(Class&& val) : m_type(std::move(val.m_type))
{
std::cout << "move: " << m_type << std::endl;
};
void print()
{
std::cout << "print: " << m_type << std::endl;
}
void set(const std::string val )
{
m_type = val;
}
private:
std::string m_type;
};
int main ()
{
Class cc("3.2");
Class c2(std::move(cc));
c2.print( );
cc.print();
cc.set( "4.0" );
cc.print();
return 0;
}
And finally the output:
defaultish
move: 3.2
print: 3.2
print:
print: 4.0
Upvotes: 1
Views: 398
Reputation: 76305
In the code example, std::move
determines which constructor gets called. Nothing more. So c2(std::move(cc))
calls the move constructor for Class
. The move constructor for Class
doesn't do anything to its argument, so cc
is unchanged, and it can be used just as it could have before the call to the move constructor.
All the talk in comments and answers about the state of an object that has been moved from is about the requirements on standard library types, which will be left in a "valid but unspecified state" (17.6.5.15, [lib.types.movedfrom]). What you do with your types is not affected by that.
EDIT: sigh. You edited the code and changed the question. Now that your class holds a std::string
instead of a float
things are different, and the std::string
object in cc
is, indeed, in a "valid but unspecified state".
Upvotes: 3
Reputation: 385144
Because the standard says so.
Moved-from objects have a valid but unspecified state. That means you can still use them, but you can't be sure what state they'll be in. They could look just as they did before the move, depending on what is the most efficient way to "move" data out of them. For example, "moving" from an int
makes no sense (you'd have to do extra work to reset the original value!) so a "move" from an int
is actually only ever going to be a copy. The same is true of a double
.
Although in this case it's got more to do with the fact that you didn't actually move anything.
Upvotes: 10