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?
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?
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
.
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.std::move
. (A particular class might cause an assertion, but that would be a very odd design.)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.
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
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
.
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!)