1

If an object is actually moved to another location, what are the operations supported on the original object?

To elaborate it, I have a type T with available move constructor. With the following statements

T x{constructor-args};
T y{std::move(x)};

what all can be done with the object x (provided that the object actually moves from x to y using available move constructor of T)?

Specifically,

  • x can be destroyed for sure. Can I assume x is trivially destructible after move?
  • can x be assigned or move assigned to a new object ( I guess yes, as I have seen many swap uses this)?
  • can I construct a new object object in place of x? If by any chance this is allowed, then is it possible to have a uninitialized_move which works with (partially) overlapped source & destination range, like std::copy and unlike std::uninitialized_copy ?

    T z{some-args};
    x = z; //or x = std::move(z);
    T u{some-args};
    new(&x) T{std::move(u)}; // or new(&x) T{u}; etc
    
abir
  • 1,797
  • 14
  • 26

2 Answers2

3

Whatever you define it to be. At the least, the resulting object should be destructable—its destructor will be called, and the state of the object should be such that that causes no problems.

The standard library guarantees that its objects are some coherent state. You can call any of the member functions: it is unspecified what you get in return, but the results will be coherent: if you call size on a vector which has been moved, you can also call operator[] with an index less than the value returned by size (but in the only reasonable implementation, size will return 0,). This is probably more than what is needed for effective use, however; all that is really necessary is that calling the destructor will work.

To make it clearer, using std::vector as an example: if we suppose the usual implementation, with three pointers, begin, end and limit (or however they are called: limit is meant to be one past the end of the allocated memory, so that capacity() returns limit - begin). After moving, the standard would require that all three pointers be null. In most implementations, the limit pointer will not be accessed in the destructor, so the looser requirements of being deletable would be met if only the begin and end pointers were set to null.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • So, I assume assignment (copy & move) are allowed, and Not placement construct. Then, say given three pointers in order p1,p2,p3, where [p1,p2) is raw memory, and [p2,p3) contains some objects of type T, what is the best way to move them in the range [p1,p1+(p3-p2)) ? My guess, `uninitialized_copy` with `move_iterator` is not going to work, as some objects may get move constructed on a moved-from location. – abir Feb 21 '14 at 12:49
  • @abir Do you want to copy them, or move them? (But I'm not sure what your question is.) – James Kanze Feb 21 '14 at 13:37
  • Move the objects on [p2,p3) a few places back, starting from p1. So i guess, I have to do some combination of `uninitialized_move` and `move` depending on the raw memory [p1,p2) and memory containing moved objects [p2,p3+p1-p2) respectively. – abir Feb 21 '14 at 13:47
2

With regards to library types, the standard says (17.6.5.15)

Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

This means they should, at a minimum, be destructible and assignable. As James mentions below, the object should still comply with its own interface. Your own types should follow the same idea.

You should not be using placement-new for this sort of thing. Pretend the move constructor doesn't exist and write code the same way as before, using operator=. The last line in your example should be x=std::move(u); or x=u;.

spraff
  • 32,570
  • 22
  • 121
  • 229
  • 2
    It means a lot more than that. For example, a valid but unspecified state for `std::vector` means that if `v` has been moved, you can still call `v.size()`, `for ( size_t i = 0; i != v.size(); ++ i ) v[i];` must still be valid, and `v.push_back( something );` must work. In practice, supporting this if you also support destruction is not significantly more work for `std::vector`, but for more complex types, it could make a difference. – James Kanze Feb 21 '14 at 11:47
  • You're right, but I'm talking about the *most general* requirements for a sensible move operator. – spraff Feb 21 '14 at 13:01
  • Yes. That's exactly my point. The most general requirements are less than those the standard imposes on its own types. – James Kanze Feb 21 '14 at 13:38