When to make a type non-movable in C++11?
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.
Short answer: If a type is copyable, it should also be moveable. However, the reverse is not true: some types like std::unique_ptr
are moveable yet it doesn't make sense to copy them; these are naturally move-only types.
Slightly longer answer follows...
There are two major kinds of types (among other more special-purpose ones such as traits):
Value-like types, such as
int
orvector<widget>
. These represent values, and should naturally be copyable. In C++11, generally you should think of move as an optimization of copy, and so all copyable types should naturally be moveable... moving is just an efficient way of doing a copy in the often-common case that you don't need the original object any more and are just going to destroy it anyway.Reference-like types that exist in inheritance hierarchies, such as base classes and classes with virtual or protected member functions. These are normally held by pointer or reference, often a
base*
orbase&
, and so do not provide copy construction to avoid slicing; if you do want to get another object just like an existing one, you usually call a virtual function likeclone
. These do not need move construction or assignment for two reasons: They're not copyable, and they already have an even more efficient natural "move" operation -- you just copy/move the pointer to the object and the object itself doesn't have to move to a new memory location at all.
Most types fall into one of those two categories, but there are other kinds of types too that are also useful, just rarer. In particular here, types that express unique ownership of a resource, such as std::unique_ptr
, are naturally move-only types, because they are not value-like (it doesn't make sense to copy them) but you do use them directly (not always by pointer or reference) and so want to move objects of this type around from one place to another.
Actually when I search around, I found quite some types in C++11 are not movable:
- all
mutex
types(recursive_mutex
,timed_mutex
,recursive_timed_mutex
, condition_variable
type_info
error_category
locale::facet
random_device
seed_seq
ios_base
basic_istream<charT,traits>::sentry
basic_ostream<charT,traits>::sentry
- all
atomic
types once_flag
Apparently there is a discussion on Clang: https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/pCO1Qqb3Xa4