When to make a type non-movable in C++11?
Asked Answered
T

4

135

I was surprised this didn't show up in my search results, I thought someone would've asked this before, given the usefulness of move semantics in C++11:

When do I have to (or is it a good idea for me to) make a class non-movable in C++11?

(Reasons other than compatibility issues with existing code, that is.)

Tillio answered 13/1, 2013 at 11:5 Comment(16)
When a class has std::mutex, should make it non-movable?Vexillum
boost is always one step ahead - "expensive to move types" (boost.org/doc/libs/1_48_0/doc/html/container/move_emplace.html)Bricky
With regards to std::mutex this thread is useful information and possibly answers your question, in an indirect way, too.Tiki
Great example @billz, I hadn't thought of threading; please post it as an answer! Are there any other situations too though, if I'm not doing any threading?Tillio
@SChepurin: "Expensive to move" isn't terribly clear... I mean, if something is too expensive for your use case then you should probably avoid it, whether it's in real life or in programming or in a game :-) It doesn't tell me anything I didn't already know.Tillio
@Steve: Yup, billz just pointed that out; that's one situation, would love to know if there are others (where threading/synchronization isn't the problem issue).Tillio
@Mehrdad - Thus, they tried to avoid it. It means somebody (boost) already "asked this before".Bricky
I think this is a very good and useful question (+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.Odom
AFAIK movable classes can still be subject to slicing, so it makes sense to forbidding moving (and copying) for all polymorphic base classes (i.e. all base classes with virtual functions).Gayl
Can we please clarify what "movable" means exactly? Does "movable" mean that there is a move ctor or just that a move request is legal (and might fall back on copying)?Rapallo
@sellibitze: I don't understand the distinction. What kind of a type would have a copy constructor (or assignment operator) but not a move constructor (or assignment operator)? I always thought that a type which is copyable would also turn out to be movable...Tillio
@Mehrdad: I'm just saying that "T has a move constructor" and "T 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
@sellibitze: T has a move-constructor.Tillio
"What kind of a type would have a copy constructor [...] but not a move constructor [...]?" -- One written for C++03 and not updated yet. Such a type is MoveConstructible and MoveAssignable according to the standard, because a copy is a degenerate move.Uncleanly
@Mehrdad: Check out the C++ standard library section on what "MoveConstructible" means. Some iterator might not have a move constructor, but it still is MoveConstructible. Watch out for varying definitions of "movable" people have in mind.Rapallo
How about "because you don't want to care to prove that moving is well defined for your type" does it count ? you just make it nonmovable until needed. when it is, you implement move semantic correctly. I'm just guessing though. but in C++03 i have a habit of making all my types noncopyable by default, simply to code less and care less. because YAGNI.Noyade
U
114

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_strongon 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.

Uncleanly answered 13/1, 2013 at 14:52 Comment(23)
+1 a less exotic example of something that can't be moved because it has a special address is a node in a directed graph structure.Gantry
I don't think the implementation has to protect against moving a std::mutex while another thread is locking it any more that it has to protect against destroying a std::mutex which another thread is trying to lock it. Both represent severe programming errors on the part of the user. A mutex allows you to create synchronized access to other data; it doesn't lift the requirement of proper lifetime management for the mutex object itself.Prance
If the mutex is non-copyable and non-movable, how can I copy or move an object which contains a mutex? (Like a thread safe class with it's own mutex for synchronization...)Malapert
@BenVoigt, right, but for the lowest-level building blocks like mutexes and atomics it's helpful to know that even motivated idiots can't move a mutex you're using. If a mutex hasn't started its destructor you can safely lock or unlock it. (discounting trying to unlock mutexes you don't own and other logic errors)Uncleanly
@tr3w, you can't, unless you create the mutex on the heap and hold it via a unique_ptr or similarUncleanly
@BenVoigt, also remember that an explicit design requirement of std::mutex was that it be eligible for static initialization, so a global std::mutex is guaranteed to be usable before dynamic initialization begins, precisely to simplify "proper lifetime management". Knowing it's non-movable also simplifies things.Uncleanly
@tr3w: Wouldn't you just move the entire class except the mutex part?Tillio
@Mehrdad: You can't not move the mutex part. The old object is expiring. The only solution is to forbid move completely.Prance
@BenVoigt, but the new object will have its own mutex. I think he means having user-defined move operations which move all members except the mutex member. So what if the old object is expiring? Its mutex expires with it.Uncleanly
@BenVoigt: The way I see it, the mutex part is a property of the object's "container" (i.e. storage location), not the object itself. So it should stick around as long as the container is there, not as long as the object is there.Tillio
@Mehrdad: depends on the class, I think. Suppose you move the object while holding the lock (I'll ignore the matter of how to avoid locking inversion if you do it while both source and destination are locked). Then as post-condition do you want to hold a lock on the location you moved from that generally has unspecified state, or the moved entity in its new location? That tells you whether to move all except the mutex, vs. hold a mutex by unique_ptr. Or you can forbid moving and not have to answer the question :-)Facetious
@SteveJessop: "Suppose you move the object while holding the lock"... well IMO the problem stems from that premise. It's wrong to begin with, so there's no right solution. On the other hand, there's no reason to forbid moving in all cases -- what if you want to move the object when people are not holding locks on it (e.g. right when the object is created)? No reason to entirely forbid it just because "there exists a situation in which moving should be forbidden".Tillio
@Mehrdad: in which case it makes no external difference which way you implement the lock, it's an implementation detail. Your way might be more performant (less dynamic allocation) whereas Jonathan's way potentially saves having to write any constructors at all. So then I don't think you'd necessarily move the whole object except the mutex (as you said to tr3w), but I also don't think you must forbid moving (as Ben said to you). You shouldn't write error-prone ctor code just to make the lock "part of the location", though. It's irrelevant what it protects if it's guaranteed unlocked!Facetious
@SteveJessop: I'm having trouble following your reasoning, e.g. "I don't think you'd necessarily move the whole object except the mutex"... I didn't quite follow why you think that. A mutex's identity is determined by its storage location, not its contents, so it should never be moved. Why do you think it should be moved in some cases?Tillio
It suddenly occurs to me, though, maybe your solution is a MutexForMovableTypes class, that wraps a mutex and is movable and whose move constructor does nothing to the source. That avoids writing error-prone boilerplate in the move ctor of the class that has one as a data member.Facetious
@Mehrdad: I didn't say the mutex should be moved, I said "moving the whole object with the exception of a mutex data member" is not the only valid solution. Another valid solution is Jonathan's - not to have a mutex member at all, instead to hold one by a smart pointer that is moved. Quite aside from anything else it needn't be a unique_ptr that you hold it by, your class could allow you to fine-tune your locking granularity if it's a shared_ptr :-)Facetious
@SteveJessop, I've used a generic type like your MutexForMovableTypes, which wraps an object but does nothing when copied/assigned. It allows a class that contains it as a data member to have implicitly-defined copy/move operations but not update that member. Useful when e.g. a class has a GUID or other identity which should be fixed and not transferred (like its address).Uncleanly
(I'll ignore the matter of how to avoid locking inversion if you do it while both source and destination are locked) You use std::lock(this->mx, that.mx) which is deadlock-free, of course :)Uncleanly
@SteveJessop: Oh I see, I didn't mean to claim that's the "only" valid solution for mutexes in general, sorry if that confused you. I meant that it's the "only" valid solution assuming you have a mutex object to begin with. Yes, if you have a unique_ptr then the situation changes entirely, but I wasn't talking about that.Tillio
@Mehrdad: in that case I agree with you, if the mutex is part of the object (not the value), then it can be a data member and unmovable, but the surrounding class could be movable anyway, perhaps with the restriction that it's UB to do it with the mutex locked. Ben was basically saying "never mind all that, you have to make it non-movable" when he disagreed with you, but that's only true presuming that the mutex identity is supposed to be part of the value (rather than the object). And that all depends on the class, I think, which is where I started ;-)Facetious
Challenge: create a standards compliant int that is movable. How about an int that stores its overflow state (as signed overflow is undefined behavior, we have lots of latitude). When copied, both have the overflow flag -- when moved-from, the overflow flag travels to the new int. Assigning to the int while it has an overflow flag causes a trap to be set (exception, etc) as you lost track of the overflow. has_overflow and clear_overflow let you read/remove said overflow. As everything happens after UB, I think this is legal? Second question: is it a moveable int?People
The answer is not complete. Only gives one example.Haland
Coming back a decade later to say I actually disagree with this answer now. (Actually, for a while now.) The ideal implementation of mutex(mutex&&) isn't = delete, it's : mutex() {}. That is to say, a mutex should be movable, but it's effectively an empty type for moving purposes.Tillio
D
58

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):

  1. Value-like types, such as int or vector<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.

  2. 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* or base&, 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 like clone. 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.

Dehydrogenase answered 13/1, 2013 at 11:47 Comment(12)
I don't know how valid your claim is... there's a Dr. Dobbs article for a ref-counted class (perfect example of the inheritance you're talking about) where they specifically implement swap as a no-op (because that's the correct implementation -- it measures the lifetime of the storage location, which is unaffected by the object's data/movement/etc.). Obviously if you wouldn't want to implement move then you wouldn't want to implement swap either, but they do it and it's the correct thing to do.Tillio
Would the real Herb Sutter please stand up? :)Herdsman
Yeah, I switched from using one OAuth Google account to another and cant be bothered to look for a way to merge the two logins that gives me here. (Yet another argument against OAuth among much more compelling ones.) I probably won't use the other one again, so this what I'll use for now for the occasional SO post.Dehydrogenase
I thought that std::mutex was immovable, as POSIX mutexes are used by address.Playpen
@SChepurin: Actually, that is called HerbOverflow, then.Odom
@Herb: I flagged your comment for a moderator to merge your accounts.Odom
As DeadMG says, std::mutex is certainly the wrong example. std::mutex cannot be moved nor copied. std::unique_lock is the movable lockable type.Mannuela
As others have said, std::mutex isn't movable, it doesn't own a resource, it is a resource. The native OS mutex type can/should be embedded right in the std::mutex object to allow it to be statically initialized via its constexpr constructor. No resource allocation involved. std::thread would be a better example.Uncleanly
This is getting a lot of upvotes, has noone noticed it says when a type should be move-only, which is not the question? :)Uncleanly
Indeed, instead of std::mutex the good example would be std::unique_lock.Malapert
@JonathanWakely. I did notice, but don't think it warrants a downvote.... For newbies into C++11/move-semantics Herb's answer is a good guideline...Wolgast
My downvote stands… there are probably already other questions for this answer. Although, @Mehrdad will certainly select Jonathan's answer and that will go to the top regardless.Gantry
V
22

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

Vexillum answered 14/1, 2013 at 4:42 Comment(9)
... iterators should not be movable?! What... why?Tillio
yeah, I think iterators / iterator adaptors should be edited away as C++11 has move_iterator?Vexillum
Okay now I'm just confused. Are you talking about iterators that move their targets, or about moving the iterators themselves?Tillio
I think you are mistaking objects without explicit move operations (or which cannot be moved more efficiently than copied) for non-movable types. But in the end all types that are copyable are also moveable. Especially all iterators are copyable and thus moveable, not just std::move_iterator (which has a totally different purpose, anyway). Likewise are std::time_points and std::durations (and maybe others I didn't check/think about more thoroughly).Allopath
So is std::reference_wrapper. Ok, the others indeed seem to be non-movable.Allopath
@ChristianRau thanks for the comment. I'll edit iterator/iterator adaptors away from answer. Also I've posted another question, regarding iterator movable: #14314910Vexillum
@Vexillum Still not enough unfortunately. Read the comments and follow the links, assuming cppreference correctly mirrors the standard there (not that it would make much sense for those types to be non-copyable by simple reasoning).Allopath
@ChristianRau yup. regarding Potatoswatter's answer on another question, I've removed more. will check others up though.Vexillum
These seem to fall into three categories: 1. low-level concurrency-related types (atomics, mutexes), 2. polymorphic base classes (ios_base, type_info, facet), 3. assorted weird stuff (sentry). Probably the only unmovable classes an average programmer will write are in the second category.Gayl
M
1

Another reason I've found - performance. Say you have a class 'a' which holds a value. You want to output an interface which allows a user to change the value for a limited time (for a scope).

A way to achieve this is by returning a 'scope guard' object from 'a' which sets the value back in its destructor, like so:

class a 
{ 
    int value = 0;

  public:

    struct change_value_guard 
    { 
        friend a;
      private:
        change_value_guard(a& owner, int value) 
            : owner{ owner } 
        { 
            owner.value = value;
        }
        change_value_guard(change_value_guard&&) = delete;
        change_value_guard(const change_value_guard&) = delete;
      public:
        ~change_value_guard()
        {
            owner.value = 0;
        }
      private:
        a& owner;
    };

    change_value_guard changeValue(int newValue)
    { 
        return{ *this, newValue };
    }
};

int main()
{
    a a;
    {
        auto guard = a.changeValue(2);
    }
}

If I made change_value_guard movable, I'd have to add an 'if' to its destructor that would check if the guard has been moved from - that's an extra if, and a performance impact.

Yeah, sure, it can probably be optimized away by any sane optimizer, but still it's nice that the language (this requires C++17 though, to be able to return a non-movable type requires guaranteed copy elision) does not require us to pay that if if we're not going to move the guard anyway other than returning it from the creating function (the dont-pay-for-what-you-dont-use principle).

Malacology answered 30/9, 2017 at 23:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.