Search code examples
c++c++11mutexc++14stdatomic

Adding a move only type to a class makes that class as a move only type?


In Scott Meyers' "Effective Modern C++", he writes:

It's worth nothing that because std::mutex is a move-only type (i.e., a type that can be moved, but not copied), a side effect of adding m to Polynomial is that Polynomial loses the ability to be copied. It can still be moved, however.

Here, he adds a std::mutex m as a member variable to a class Polynomial in the context of explaining why one should protect access to const member variable of a class(while multi-threading). I understood the concept he explained. but one thing which I need to have some more explanation is "why Adding a move only type to a class makes that class as move only type not copy-able"?, why types like std::mutex and std::atomic is move only type by default? how can we get over with it if we want to perform copy operation even when adding move only type of variable to our class?


Solution

  • std::mutex is not copyable only because the standard says so. They could probably have written a std::mutex that was copyable, but they decided not to.

    As a guess, a non-copyable std::mutex might be more efficient, safer, or easier to understand.

    As an example, here is why it might be safer/easier to understand: What does it mean to copy a std::mutex that is locked? I, personally, have no idea what the right answer is; I suspect that the right answer is "that is nonsense". There is no "answer of least surprise".

    Instead of having a surprising answer in concurrency code, by blocking copy we avoid the question. People storing a mutex who want to copy have to decide themselves what they want it to do.


    C++ will under some circumstances automatically generate a copy constructor. Under more circumstances it will let you do MyClass(MyClass const&)=default and write one when you ask for it.

    If your class contains a non-copyable class, it won't generate one, and you cannot ask for it. This is because the generated copy constructor basically copies its members; if you cannot copy a member, it cannot generate a copy constructor.


    So mutex cannot be copied because the standard says so.

    If a struct or class contains a non-copyable member, the default copy constructor cannot be used. You have to write one yourself explicitly.


    If your type needs to store a mutex, one approach to prevent the headache of having to maintain a copy ctor that manually copies all of its other members is to stick your non-mutex state into a sub-struct, and then use its default copy. Then you decide what to do with the mutex during the copy, and your copy ctor (and assignment copy operator) remain simple.

    struct bob_with_mutex {
      sturct bob_simple_data {
        int x, y, z;
        std::vector<char> buff;
      };
    
      bob_simple_data data;
      std::mutex m;
    
      bob_with_mutex( bob_with_mutex const& o ):
        data(o.data)
      {}
      bob_with_mutex& operator=( bob_with_mutex const& o )
      {
        data = o.data;
        return *this;
      }
    };
    

    the type bob has a mixture of a mutex and some data. The data is stored in a sub-structure. The copy ctor of bob_with_mutex cannot be defaulted, but it simply says "copy the data, and nothing else".

    The copy of the bob has its own mutex.