3

Possible Duplicate:
What can I do with a moved-from object?

For example, see this code:

template<class T> 
void swap(T& a, T& b) 
{ 
    T tmp(std::move(a));
    a = std::move(b); 
    b = std::move(tmp);
} 

Is it just me, or is there a bug here? If you move a into tmp, then doesn't a become invalid?

i.e. Shouldn't the move-assignment to a from b be a move-constructor call with placement new instead?
If not, then what's the difference between the move constructor and move assignment operator?

template<class T>
void swap(T& a, T& b)
{
    T tmp(std::move(a));
    new(&a) T(std::move(b));
    new(&b) T(std::move(tmp));
}
Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • [related FAQ](http://stackoverflow.com/a/11540204/252000) – fredoverflow Jul 27 '12 at 16:13
  • @FredOverflow: Thanks for the links, those are helpful. But I don't get it: if move-assignment *doesn't* require a valid target to begin with, then what's the point of move-construction? Aren't they the same thing then? – user541686 Jul 27 '12 at 16:18
  • @Mehrdad: Suppose you have an object whose construction and copy operations are costly. When you create a temporary object (for example an r-valued one) you are first calling the costly constructor, then the costly copy constructor. But with a move constructor the costly copy is avoided, and in its place a more light copy operation can be done (for example copying the pointer to an allocated array instead of allocating a new array and copying its content). – Gigi Jul 27 '12 at 16:26
  • 2
    @Mehrdad You are still confused. Just like copy assignment, move assignment *does* require a valid target object. Moving from an object does *not* invalidate it. A moved-from object is still an object and needs to be destructed just like any other object. If a moved-from object was somehow invalid, almost any non-trivial destructor would wreak havoc in the system when trying to destruct it. – fredoverflow Jul 27 '12 at 17:20

3 Answers3

11

When you move data out of an object, the intended semantics is that the object that was moved from ends up in an unspecified but valid state. That means that you can't predict anything about what state the object will be in other than that it will be a well-formed object. This is not quite the same as "this object is dead and gone." Moving data out of an object doesn't end the object's lifetime - it just changes its state to something unspecified - and consequently it's perfectly safe to assign that object a new value.

As a result, the initial version of the swap function is safe. After moving data from a, that object holds some unspecified "safe but unpredictable" value. This value is then overwritten when move-assigning it the value of b.

That second version is unsafe, because the lifetime of a has not ended before you try to construct a new object on top of it. This leads to undefined behavior.

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • The lifetime ends as soon as you construct a new object on top of it, IIRC. – Xeo Jul 27 '12 at 16:12
  • What if the object doesn't *have* a state that represents an "unreadable state"? (Say, a `unique_ptr` that doesn't have a "null" value of some sort.) What would be its state after `a` is moved to `tmp`? – user541686 Jul 27 '12 at 16:13
  • @Xeo: according to this example: http://ideone.com/3AVC6 it doesn't – akappa Jul 27 '12 at 16:15
  • 1
    @Mehrdad: It's value is _unspecified_. You shouldn't rely on it having a specific value, even if it make sense for the object to have an _empty_ value. – Gigi Jul 27 '12 at 16:17
  • @akappa: Sure, the destructor isn't invoked, but the lifetime ends anyways. – Xeo Jul 27 '12 at 16:17
  • 2
    @Xeo Not sure what you mean here, but moving from an object definitely does *not* end its lifetime. The lifetime ends when the destructor starts, and that does not happen when you move from an object. – fredoverflow Jul 27 '12 at 16:18
  • @Xeo: what do you mean by "lifetime", then? For me, when the lifetime of an object ends, its destructor gets called. – akappa Jul 27 '12 at 16:20
  • 1
    @akappa that's undefined behaviour. And Xeo is right *for the cases when doing that is not undefined behaviour*. Standard reference: §3.8 paragraph 4. – R. Martinho Fernandes Jul 27 '12 at 16:23
  • @akappa: It feels like calling `a` "alive" is like saying "Hey, even though I just transplanted every single one of your organs (brain, heart, lungs, etc.) inside someone else, you're still considered alive until someone buries you!!" Might it not as well be dead? – user541686 Jul 27 '12 at 16:25
  • 1
    @Mehrdad: or until you get a new brain, heart, etc. from someone else. – akappa Jul 27 '12 at 16:26
  • @R.MartinhoFernandes: we were talking about `new(&a) T(std::move(b));`. – akappa Jul 27 '12 at 16:27
  • 1
    @akappa: He knows that, and it's not undefined behaviour for POD types IIRC. – Xeo Jul 27 '12 at 16:39
  • _"the value stored here is not supposed to be read, and you're note supposed to do anything to this object except assign it a new value."_ **No!** it is in a valid, but unspecified state. That does **not** mean you can only assign to it, I wish people would stop propagating that nonsense. – Jonathan Wakely Jul 28 '12 at 15:20
  • @Jonathan Wakely- Can you elaborate on this? My understanding was that moving from an object let the programmer do whatever they wanted to that object, so long as the destructor wouldn't crash later on. Is this incorrect? If it is, can you point me at a reference explaining what move functions can and can't do? – templatetypedef Jul 28 '12 at 19:34
  • see http://stackoverflow.com/a/7028318/981959 - that applies to all standard library types after you move from them. For your own types you're free to make the object "only assignable or destroyable" post-move if you want, but you'd better document that clearly, and it's not correct to say those are the intended semantics in general. – Jonathan Wakely Jul 28 '12 at 21:03
  • @JonathanWakely- Thanks! That definitely helps! – templatetypedef Jul 28 '12 at 21:07
  • For example, after moving from a string with `std::string s(std::move(orig))`, it is perfectly OK to do `if (orig.size()>1) orig[1]='a';` because `string::size()` has no preconditions, and the precondition for `orig[1]` is know to be met after checking the size. (The condition could be true if `string` uses the small-string optimisation and doesn't zero its length after a move.) – Jonathan Wakely Jul 28 '12 at 21:30
6

a will still be valid. The data within a will not be reliable any more though. You are not deallocating a when you move its resources, so it's perfectly fine to move new resource to a.

Man of One Way
  • 3,904
  • 1
  • 26
  • 41
  • So what's the difference between the move construction and move assignment? – user541686 Jul 27 '12 at 16:04
  • 2
    @Mehrdad one creates a new object, the other doesn't? – R. Martinho Fernandes Jul 27 '12 at 16:11
  • Er... if the object is already moved out of the storage space of `a`, then isn't `a` dead? What if `a` doesn't *have* a notion of an "invalid state"? – user541686 Jul 27 '12 at 16:16
  • 3
    It's not `a` that is moved, it's the resources within `a`. If you have a pointer in `a` to some memory, for example `a->data`, and you move `data` to another object, `a` will still exist. – Man of One Way Jul 27 '12 at 16:18
  • @Mehrdad *it needs that notion* to be movable, at least internally (i.e., it doesn't have to be observable from the outside, but the object needs to know, for when it is assigned). – R. Martinho Fernandes Jul 27 '12 at 16:21
  • @R.MartinhoFernandes: Huhhh... very interesting (doesn't make sense to me, but at least it corresponds with what I'm seeing). So let's say I use the move-constructor version instead. What could possibly go wrong with that? – user541686 Jul 27 '12 at 16:23
  • 1
    @Mehrdad: The move-constructor has undefined behaviour for non-POD types; you're generally not allowed to construct an object on top of an existing object. For example, `a` might still own some resource despite having been moved, in which case your code would leak that resource. You could cobble something almost valid together by calling the destructor first, but assignment is the way to give an existing object a new value. – Mike Seymour Jul 27 '12 at 16:35
  • @MikeSeymour: Interesting... thanks for the info. – user541686 Jul 27 '12 at 16:47
5

Moving does not make an object invalid. Rather, it remains in a valid but indeterminate state in general. Specific classes have additional guarantees; for example, std::unique_ptr guarantees that it will be null after being moved from.

Remaining a valid object means in particular that it is perfectly fine to assign to the object, which is what the original code does.

Your own proposed solution is heavily broken: When you placement-construct a new object on top of the old one, the lifetime of the old object ends. However, if the destructor of the class has effects, then omitting to call the destructor is undefined behaviour.

Moreover, if you did correctly call the destructor first, but then encountered an exception in the constructor, you'd be in trouble, as you now don't have a valid object which needs to be destroyed at scope exit. Here's a related question of mine on this topic.

Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084