26

After std::move is called on an object, why doesn't the language cause a compilation error if the object is used after?

Is it because it is not possible for compiler to detect this condition?

fish2000
  • 4,289
  • 2
  • 37
  • 76
Jubin Chheda
  • 534
  • 4
  • 11
  • 3
    Because you can do things with it and you can reuse it. Moving leaves the object in a valid state. – NathanOliver Mar 14 '17 at 20:15
  • 3
    The state of the moved object is defined by the move constructor. Hence it is ok to use it as long as the move constructor allows it. – Daniel Mar 14 '17 at 20:16
  • 2
    `std::move` just casts the object to `T&&`, it doesn't actually do anything to the object. – lcs Mar 14 '17 at 20:19
  • 2
    (1) `std::move` is just a cast no data changes, so do need for the compiler to take any action. (2) Doing something with the result of `std::move` is too complex for analysis in most cases. (3) A lot of questions on SO would not have been asked if `std::move` had been implemented as `rvalue_ref_cast` – Richard Critten Mar 14 '17 at 20:19
  • 7
    `move` follows a nice old C++ tradition. Just like `move` doesn't move, `remove` doesn't remove and `delete` does not delete its argument :) – Christian Hackl Mar 14 '17 at 20:26
  • @RichardCritten: Perhaps an even better name would be `std::allow_move`. The real problem is that the decision whether to move might be a run-time decision, and the later use may also be conditional, so the compile simply cannot prove whether the two will conflict. (and that's ignoring separate compilation) – MSalters Mar 15 '17 at 08:36
  • I think the C++ comittee should’ve thought more before finalising move constructors. Though it can still be removed in future versions, because none uses this feature at all. IMO, it's rather harmful than useful ;( – tripulse Nov 15 '20 at 15:10

5 Answers5

14

The general principle in C++ language design is "trust the programmer". Some issues I can think of with rejecting any use of the object after it has been an argument to std::move.

  • To determine whether a given use is after a call to std::move in the general case would be equivalent to solving the halting problem. (In other words, it can't be done.) You would have to come up with some rules that describe what you mean by "after" in a way that can be statically determined.
  • In general, it is perfectly safe to assign to an object that has been an argument to std::move. (A particular class might cause an assertion, but that would be a very odd design.)
  • It's hard for a compiler to tell whether a given function is going to just assign the elements of the class new values.
  • 9
    Ah, good to know [Rust have solved the Halting Problem](http://typeinference.com/rust/2015/07/31/rust-ownership-part-i.html) – Hi-Angel Aug 02 '17 at 13:52
  • I wonder what Rust does when the move occurs in a conditional branch. It probably plays safe: if there is a branch where a move occurs, then it is presumed that it does in fact occur. But this may disallow some programs that are valid. – CygnusX1 Sep 07 '17 at 16:16
  • Yes, Rust plays it safe, even in cases where the compiler knows that a branch could never execute. The following does not compile fn main() { println!("Hello world"); let v1 = vec![1, 2, 3]; let v2 = v1; if 0 == 1 { v2 = v1; } println!("v1[0]={}", v1[0]); } – KristianR Dec 11 '17 at 22:44
  • 2
    @KristianR Thanks. So C++ rules out the Rust approach because "trust the programmer". – Martin Bonner supports Monica Dec 12 '17 at 06:29
7

Remember, std::move is nothing more than a cast to an rvalue reference. In itself it doesn't actually move anything. Additionally, the language only states that a moved from object is in a valid/destructible state but apart from that doesn't say anything about its content - it may still be intact, it may not be or (like std::unique_ptr it may be defined to have specific content (nullptr)) - it all depends on what the move constructor/move-assignment-operator implements.

So, whether or not it is safe/valid to access a moved from object depends entirely on the specific object. Reading a std::unique_ptr after a move, to see if it is nullptr is perfectly fine for example - for other types; not so much.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
4

This is what's called a "quality of implementation" issue: it makes much more sense for a good compiler or toolchain to issue a warning for a potentially dangerous use-after-move situation than for the language standard to formally forbid it in a precisely defined set of situations. After all, some uses after moves are legitimate (such as the case of a std::unique_ptr, which is guaranteed to be empty after moved from) whereas other cases that are undefined cannot be easily detected---detecting, in general, whether an object is used after it is moved from is equivalent to the halting problem.

You can use clang-tidy to detect use after move: https://clang.llvm.org/extra/clang-tidy/checks/misc-use-after-move.html

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
4

This is because we often want to use the moved-from object. Consider the default implementation of std::swap:

template<typename T> 
void swap(T& t1, T& t2)
{
    T temp = std::move(t1);
    t1 = std::move(t2);   // using moved-from t1
    t2 = std::move(temp); // using moved-from t2
}

You do not want the compiler to warn you each time you use std::swap.

Andrzej
  • 5,027
  • 27
  • 36
  • 3
    In this case, though, `t1` is in a well-behaved state after it is assigned to. And `t2` is well-behaved at the end (but `temp` isn't). Certainly a compiler could see this as OK. – Ben May 11 '18 at 14:45
  • 1
    To add to my previous comment, an assignment operator with regular semantics is perfectly reasonable to call on a moved object and will put it in a well-defined state, so if I were to write a compiler warning, I'd skip the warning for the above case. The more difficult case is that there are other member functions that make an object's state fully defined, such as `std::unique_ptr::reset`. That's in the standard, but if you write a class, there's no annotation for the compiler to know "calling this memfun puts the class in a fully-defined state". So there's no perfect warning, I guess. – Ben Aug 30 '18 at 13:17
  • 1
    That said, for typical semantics, after `std::move`, the next member function called should almost always be non-`const` before any `const` member function is called. There's no guarantee that a non-`const` member function fully defines the state of the object, but calling any `const` member function on a moved object is pretty dubious. – Ben Aug 30 '18 at 14:04
1

The nature of an object’s post-move state is defined by the implementor; in some cases, moving an object may leave it in a perfectly usable state, while in others the object may be rendered useless (or worse, somehow dangerous to use).

While I somewhat agree – it would be nice to have the option of generating a warning, at least, if an object is used in a potentially dangerous way after a move, I don’t know of options to either GCC or Clang to draw attention to this type of code smell.

(In fact, to the intrepid, this might make a fine basis for e.g. a Clang plugin, indeed!)

fish2000
  • 4,289
  • 2
  • 37
  • 76
  • 1
    The state of the object post `std::move` had NOT changed. Its just a fancy cast. It's the function that is called (move constructor move assignment operator etc), when you have cast the original object that changes its state. – Richard Critten Mar 14 '17 at 20:21
  • 2
    @RichardCritten that is true w/r/t what `std::move(…)` does, in a technical sense – but it allows an implementor to write a move constructor (and/or a move assignment operator) that does in fact manipulate an objects’ state. – fish2000 Mar 14 '17 at 20:25