7

I tested the following code:

#include <iostream>
using namespace std;
class foo{
public:
    foo()       {cout<<"foo()"<<endl;}
    ~foo()      {cout<<"~foo()"<<endl;}
};

int main()
{
    foo f;
    move(f);
    cout<<"statement \"move(f);\" done."<<endl;
    return 0;
}

The output was:

foo()
statement "move(f);" done.
~foo()

However, I expected:

foo()
~foo()
statement "move(f);" done.

According to the source code of the function move:

  template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

The returned object is a right value, So Why isn't it destroyed immediately?



-----------------------------------------------------------------
I think I just confused rvalue and rvalue reference.
I modified my code:

#include <iostream>

template<typename _Tp>
constexpr typename /**/std::remove_reference<_Tp>::type /* no && */
/**/ mymove /**/ (_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

using namespace std;
class foo{
public:
    foo()       {cout<<"foo() at "<<this<<endl;} /* use address to trace different objects */
    ~foo()      {cout<<"~foo() at "<<this<<endl;} /* use address to trace different objects */
};

int main()
{
    foo f;
    mymove(f);
    cout<<"statement \"mymove(f);\" done."<<endl;
    return 0;
}

And now I get what I've been expecting:

foo() at 0x22fefe
~foo() at 0x22feff
statement "mymove(f);" done.
~foo() at 0x22fefe
4

6 回答 6

15

Moving from an object doesn't change its lifetime, only its current value. Your object foo is destroyed on return from main, which is after your output.

Futhermore, std::move doesn't move from the object. It just returns an rvalue reference whose referand is the object, making it possible to move from the object.

于 2013-03-27T16:20:03.447 回答
6

Objects get destroyed when they go out of scope. Moving from an object doesn't change that; depending on what the move constructor or move assignment operator does, the state of the object can be different after the move, but is hasn't yet been destroyed, so the practical rule is that moving from an object must leave it in a state that can be destroyed.

Beyond that, as @R.MartinhoFernandes points out, std::move doesn't do anything. It's the object's move constructor or move assignment operator that does whatever needs to be done, and that isn't applied in a call to std::move; it's applied when the moved-from object is used to construct a new object (move constructor) or is assigned to an existing object (move assignment operator). Like this:

foo f;
foo f1(f);            // applies foo's copy constructor
foo f2(std::move(f)); // applies foo's move constructor
foo f3, f4;
f3 = f;               // applies foo's copy assignment operator
f4 = std::move(f1);   // applies foo's move assignment operator
于 2013-03-27T16:20:45.187 回答
2

A std::move doesn't change objects lifetime. Roughly speaking it's nothing more than a static_cast that casts a non const lvalue to a non const rvalue reference.

The usefulness of this is overload resolution. Indeed, some functions take parameters by const lvalue reference (e.g. copy constructors) and other take by non const rvalue reference (e.g. move constructors). If the passed object is a temporary, then the compiler calls the second overload. The idea is that just after the function is called the a temporary can no longer be used (and will be destroyed). Therefore the second overload could take ownership of the temporary's resources instead of coping them.

However, the compiler will not do it for a non-temporary object (or, to be more correct for an lvalue). The reason is that the passed object has a name that remains in scope and therefore is alive could still be used (as your code demonstrate). So its internal resources might still be required and it would be a problem if they have had been moved to the other object. Nevertheless, you can instruct the compiler that it can call the second overload by using std::move. It casts the argument to a rvalue reference and, by overload resolution, the second overload is called.

The slight changed code below illustrate this point.

#include <iostream>

using namespace std;

class foo{
public:
    foo()  { cout << "foo()"  << endl; }
    ~foo() { cout << "~foo()" << endl; }
};

void g(const foo&) { cout << "lref" << endl; }
void g(foo&&)      { cout << "rref" << endl; }

int main()
{
    foo f;
    g(f);

    g(move(f));

    // f is still in scope and can be referenced.
    // For instance, we can call g(f) again.
    // Imagine what would happen if f had been destroyed as the question's author
    // originally though?

    g(static_cast<foo&&>(f)); // This is equivalent to the previous line

    cout<<"statement \"move(f);\" done."<<endl;
    return 0;
}

The output is

foo()
lref
rref
rref
statement "move(f);" done.
~foo()

Update: (After the question has been changed to use mymove)

Notice that the new code doesn't give exactly what you said at the very beginning. Indeed it reports two calls to ~foo() rather than one.

From the displayed addresses we can see that the original object of type foo, namely, f is destroyed at the very end. Exactly as it used to be with the original code. As many have pointed out, f is destroyed only at the end of its scope (the body of function main). This is still the case.

The extra call to ~foo() reported just before the statement "mymove(f);" done. destroys another object which is a copy of f. If you add a reporting copy constructor to foo:

foo(const foo& orig) { cout << "copy foo from " << &orig << " to " << this << endl;}      

Then you get an output similar to:

foo() at 0xa74203de
copy foo from 0xa74203de to 0xa74203df
~foo() at 0xa74203df
statement "move(f);" done.
~foo() at 0xa74203de

We can deduce that calling mymove yields a call to the copy constructor to copy f to another foo object. Then, this newly created object is destroyed before execution reaches the line that displays statement "move(f);" done.

The natural question now is where this copy come from? Well, notice the return type of mymove:

constexpr typename /**/std::remove_reference<_Tp>::type /* no && */`

In this example, after a simplification for clarity, this boils down to foo. That is, mymove returns a foo by value. Therefore, a copy is made to create a temporary object. As I said before, a temporary is destroyed just after the expression that creates it finishes to be evaluated (well, there are exceptions to this rule but they don't apply to this code). That explains the extra call to ~foo().

于 2013-03-27T16:27:01.733 回答
1

Because in the general case, the move could happen in another translation unit. In your example, the object wasn't even moved, only marked as movable. This means that the caller of std::move will not know if the object was moved or not, all he knows is, that there is an object and that it has to call the destructor at the end of the scope/lifetime of that object. std::move only marks the object as movable, it does not perform the move operation or create a moved copy that can be further moved or anything like that.

Consider:

// translation unit 1
void f( std::vector< int >&& v )
{
  if( v.size() > 8 ) {
    // move it
  }
  else {
    // copy it as it's just a few entries
  }
}

// translation unit 2

void f( std::vector< int >&& );
std::vector< int > g();

int main()
{
  // v is created here
  std::vector< int > v = g();

  // it is maybe moved here
  f( std::move( v ) );

  // here, v still exists as an object
  // when main() ends, it will be destroyed
}

In the example above, how would translation unit 2 decide whether or not to call the destructor after the std::move?

于 2013-03-27T16:09:15.293 回答
1

You're getting confused by the name -- std::move doesn't actually move anything. It just converts (casts) an lvalue reference into an rvalue reference, and is used to make someone else move something.

Where std::move is useful is when you have an overloaded function that takes either an lvalue or an rvalue reference. If you call such a function with a simple variable, overload resolution means you'll call the version with the lvalue reference. You can add an explicit call to std::move to instead call the rvalue reference version. No moves involved, except within the function that takes the rvalue reference.

Now the reason it is called move is the common usage where you have two constructors, one of which takes an lvalue reference (commonly called the copy constructor) and one that takes an rvalue reference (commonly called the move constructor). In this case, adding an explicit call to std::move means you call the move constructor instead of the copy constructor.

In the more general case, it's common practice to have overloaded functions that take lvalue/rvalue references where the lvalue version makes a copy of the object and the rvalue version moves the object (implicitly modifying the source object to take over any memory it uses).

于 2013-03-27T16:25:06.780 回答
0

Suppose code like this:

void bar(Foo& a) {
  if (a.noLongerneeded())
    lastUnneeded = std::move(a);
}

In this case, the caller of bar can not know that the function might in some cases end up calling the destructor of the passed object. It will feel responsible for that object, and make sure to call its destructor at any later point.

So the rule is that move may turn a valid object into a different but still valid object. Still valid means that calling methods or the destructor on that object should still yield well-defined results. Strictly speaking, move by itself does nothing except tell the receiver of such a reference that it may change the object if doing so makes sense. So it's the recipient, e.g. move constructor or move assignment operator, which does the actual moving. These operations will usually either not change the object at all, or set some pointers to nullptr, or some lengths to zero or something like that. They will however never call the destructor, since that task is left to the owner of the object.

于 2013-03-27T16:22:05.933 回答