Herb's answer (before it was edited) actually gave a good example of a type which shouldn't be movable: std::mutex
.
The OS's native mutex type (e.g. pthread_mutex_t
on POSIX platforms) might not be "location invariant" meaning the object's address is part of its value. For example, the OS might keep a list of pointers to all initialized mutex objects. If std::mutex
contained a native OS mutex type as a data member and the native type's address must stay fixed (because the OS maintains a list of pointers to its mutexes) then either std::mutex
would have to store the native mutex type on the heap so it would stay at the same location when moved between std::mutex
objects or the std::mutex
must not move. Storing it on the heap isn't possible, because a std::mutex
has a constexpr
constructor and must be eligible for constant initialization (i.e. static initialization) so that a global std::mutex
is guaranteed to be constructed before the program's execution starts, so its constructor cannot use new
. So the only option left is for std::mutex
to be immovable.
The same reasoning applies to other types that contain something that requires a fixed address. If the address of the resource must stay fixed, don't move it!
There is another argument for not moving std::mutex
which is that it would be very hard to do it safely, because you'd need to know that noone is trying to lock the mutex at the moment it's being moved. Since mutexes are one of the building blocks you can use to prevent data races, it would be unfortunate if they weren't safe against races themselves! With an immovable std::mutex
you know the only things anyone can do to it once it has been constructed and before it has been destroyed is to lock it and unlock it, and those operations are explicitly guaranteed to be thread safe and not introduce data races. This same argument applies to std::atomic<T>
objects: unless they could be moved atomically it wouldn't be possible to safely move them, another thread might be trying to call compare_exchange_strong
on the object right at the moment it's being moved. So another case where types should not be movable is where they are low-level building blocks of safe concurrent code and must ensure atomicity of all operations on them. If the object value might be moved to a new object at any time you'd need to use an atomic variable to protect every atomic variable so you know if it's safe to use it or it's been moved ... and an atomic variable to protect that atomic variable, and so on...
I think I would generalize to say that when an object is just a pure piece of memory, not a type which acts as a holder for a value or abstraction of a value, it doesn't make sense to move it. Fundamental types such as int
can't move: moving them is just a copy. You can't rip the guts out of an int
, you can copy its value and then set it to zero, but it's still an int
with a value, it's just bytes of memory. But an int
is still movable in the language terms because a copy is a valid move operation. For non-copyable types however, if you don't want to or can't move the piece of memory and you also can't copy its value, then it's non-movable. A mutex or an atomic variable is a specific location of memory (treated with special properties) so doesn't make sense to move, and is also not copyable, so it's non-movable.
+1
from me) with a very thorough answer from Herb (or his twin, as it seems), so I made it an FAQ entry. If someone objects just ping me at the lounge, so this can be discussed there. – OdomT x = std::move(anotherT);
being legal" are not equivalent. The latter is a move-request which might fall back on the copy ctor in case T has no move ctor. So, what does "movable" mean exactly? – Rapallo