49

Many classes in the c++ standard library now have move constructors, for example -

thread::thread(thread&& t)

But it appears that std::mutex does not. I understand that they can't be copied, but it seems to make sense to be able to return one from a "make_mutex" function for example. (Not saying it's useful, just that it makes sense)

Is there any reason why std::mutex doesn't have a move constructor?

jcoder
  • 29,554
  • 19
  • 87
  • 130
  • 1
    I'm having the same problem right there, because in my code I need to block access to one method until another finishes. Sadly, the object the methods run on is best created inside a factory, and due to this same limitation, I can't simply move the created "functor" out of the factory method. This thread really encourages me to experiment with `std::future` as a separation model instead. – starturtle Jun 16 '16 at 13:40
  • The idea of a `make_mutex` function is still useful, all the other discussion aside. With C++17 and guaranteed copy elision, it should be possible to do so. The mutex will not be moved - it will be constructed in-place at the caller's location, but the callee (eg: `make_mutex`) gets to decide how to do that construction. – jwd Oct 11 '21 at 04:44

2 Answers2

48

Well... mainly because I don't think they should move. Literally.

In some OS-es a mutex might be modeled as a handle (so you could copy them) but IIRC a pthreads mutex is manipulated in-place. If you are going to relocate that, any threadsafety is going fly right out of the window (how would the other threads know that the mutex had just changed it's memory address...)?

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 6
    Ok I guess that makes sense :) I was thinking of the mutex object representing the mutex so you could move that to another object, but if you think of it as *being* the mutex then it makes no sense to move it, you are right, – jcoder Sep 26 '11 at 16:03
  • 7
    @JohnB: You can do what you want with `unique_lock`. This is a lockable object (has the API of mutex), and it can be moved. Oh, but I wouldn't expose it to multiple threads at once. But you can have one in each thread that references the same `mutex`. – Howard Hinnant Sep 26 '11 at 16:53
  • 1
    This explained the issue well for me. An implication of this is that you cannot (and should not) put a mutex directly in a container. – T.E.D. Dec 03 '14 at 16:00
  • 2
    @T.E.D. Except if you can guarantee that the storage won't ever move (`std::array` is a container. So is `boost::intrusive::slist` :)). But otherwise, yes. You can always **[hack it with a `shared_ptr`](http://www.boost.org/doc/libs/1_57_0/libs/smart_ptr/sp_techniques.html#as_lock)** of sorts. – sehe Dec 03 '14 at 16:01
  • @sehe - I started a question on this topic: http://stackoverflow.com/questions/27276555/safe-and-effective-way-to-put-a-mutex-on-a-container-entry – T.E.D. Dec 03 '14 at 16:24
  • an std::vector is also manipulated in-place; you can still move it though. – einpoklum Jun 14 '20 at 16:10
  • @einpoklum And similarly, it's not safe to use it from two threads without synchronization, so catch-22. To have something that allows synchronization, it cannot move, because how would the parties synchronize? – sehe Jun 14 '20 at 17:07
21

Remember that C++ takes the "don't pay for what you don't use" philosophy to heart. Let's consider for instance an imaginary platform that uses an opaque type to represent a mutex; let's call that type mutex_t. If the interface to operate on that mutex uses mutex_t* as arguments, for instance like void mutex_init(mutex_t* mutex); to 'construct' a mutex, it might very well the case that the address of the mutex is what's used to uniquely identify a mutex. If this is the case, then that means that mutex_t is not copyable:

mutex_t kaboom()
{
    mutex_t mutex;
    mutex_init(&mutex);
    return mutex; // disaster
}

There is no guarantee here when doing mutex_t mutex = kaboom(); that &mutex is the same value as &mutex in the function block.

When the day comes that an implementor wants to write an std::mutex for that platform, if the requirements are that type be movable, then that means the internal mutex_t must be placed in dynamically-allocated memory, with all the associated penalties.

On the other hand, while right now std::mutex is not movable, it is very easy to 'return' one from a function: return an std::unique_ptr<std::mutex> instead. This still pays the costs of dynamic allocation but only in one spot. All the other code that doesn't need to move an std::mutex doesn't have to pay this.

In other words, since moving a mutex isn't a core operation of what a mutex is about, not requiring std::mutex to be movable doesn't remove any functionality (thanks to the non-movable T => movable std::unique_ptr<T> transformation) and will incur minimal overhead costs over using the native type directly.


std::thread could have been similarly specified to not be movable, which would have made the typical lifetime as such: running (associated to a thread of execution), after the call to the valued constructor; and detached/joined (associated with no thread of execution), after a call to join or detach. As I understand it an std::vector<std::thread> would still have been usable since the type would have been EmplaceConstructible.

edit: Incorrect! The type would still need to be movable (when reallocating after all). So to me that's rationale enough: it's typical to put std::thread into containers like std::vector and std::deque, so the functionality is welcome for that type.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114