Why are mutexes and condition variables trivially copyable?

后端 未结 3 1193
野的像风
野的像风 2021-02-13 20:34

LWG 2424 discusses the undesirable status of atomics, mutexes and condition variables as trivially copyable in C++14. I appreciate that a fix is already lined up, but std::mutex

相关标签:
3条回答
  • 2021-02-13 21:17

    Either it was my mistake, or I was misquoted, and I honestly don't recall which.

    However, I have this very strongly held advice on the subject:

    Do not use is_trivial nor is_trivially_copyable! EVER!!!

    Instead use one of these:

    is_trivially_destructible<T>
    is_trivially_default_constructible<T>
    is_trivially_copy_constructible<T>
    is_trivially_copy_assignable<T>
    is_trivially_move_constructible<T>
    is_trivially_move_assignable<T>
    

    Rationale:

    tldr: See this excellent question and correct answer.

    No one (including myself) can remember the definition of is_trivial and is_trivially_copyable. And if you do happen to look it up, and then spend 10 minutes analyzing it, it may or may not do what you intuitively think it does. And if you manage to analyze it correctly, the CWG may well change its definition with little or no notice and invalidate your code.

    Using is_trivial and is_trivially_copyable is playing with fire.

    However these:

    is_trivially_destructible<T>
    is_trivially_default_constructible<T>
    is_trivially_copy_constructible<T>
    is_trivially_copy_assignable<T>
    is_trivially_move_constructible<T>
    is_trivially_move_assignable<T>
    

    do exactly what they sound like they do, and are not likely to ever have their definition changed. It may seem overly verbose to have to deal with each of the special members individually. But it will pay off in the stability/reliability of your code. And if you must, package these individual traits up into a custom trait.

    Update

    For example, clang & gcc compile this program:

    #include <type_traits>
    
    template <class T>
    void
    test()
    {
        using namespace std;
        static_assert(!is_trivial<T>{}, "");
        static_assert( is_trivially_copyable<T>{}, "");
        static_assert( is_trivially_destructible<T>{}, "");
        static_assert( is_destructible<T>{}, "");
        static_assert(!is_trivially_default_constructible<T>{}, "");
        static_assert(!is_trivially_copy_constructible<T>{}, "");
        static_assert( is_trivially_copy_assignable<T>{}, "");
        static_assert(!is_trivially_move_constructible<T>{}, "");
        static_assert( is_trivially_move_assignable<T>{}, "");
    }
    
    struct X
    {
        X(const X&) = delete;
    };
    
    int
    main()
    {
        test<X>();
    }
    

    Note that X is trivially copyable, but not trivially copy constructible. To the best of my knowledge, this is conforming behavior.

    VS-2015 currently says that X is neither trivially copyable nor trivially copy constructible. I believe this is wrong according to the current spec, but it sure matches what my common sense tells me.

    If I needed to memcpy to uninitialized memory, I would trust is_trivially_copy_constructible over is_trivially_copyable to assure me that such an operation would be ok. If I wanted to memcpy to initialized memory, I would check is_trivially_copy_assignable.

    0 讨论(0)
  • 2021-02-13 21:19

    Not all implementations provide a nontrivial destructor for mutex. See libstdc++ (and assume that __GTHREAD_MUTEX_INIT has been defined):

      // Common base class for std::mutex and std::timed_mutex
      class __mutex_base
      {
      // […]
    #ifdef __GTHREAD_MUTEX_INIT
        __native_type  _M_mutex = __GTHREAD_MUTEX_INIT;
    
        constexpr __mutex_base() noexcept = default;
    #else
      // […]
    
        ~__mutex_base() noexcept { __gthread_mutex_destroy(&_M_mutex); }
    #endif
      // […]
      };
    
      /// The standard mutex type.
      class mutex : private __mutex_base
      {
        // […]
        mutex() noexcept = default;
        ~mutex() = default;
    
        mutex(const mutex&) = delete;
        mutex& operator=(const mutex&) = delete;
      };
    

    This implementation of mutex is both standard conforming and trivially copyable (which can be verified via Coliru). Similarly, nothing stops an implementation from keeping condition_variable trivially destructible (cf. [thread.condition.condvar]/6, although I couldn't find an implementation that does).

    The bottom line is that we need clear, normative guarantees, and not clever, subtle interpretations of what condition_variable does or doesn't have to do (and how it has to accomplish that).

    0 讨论(0)
  • 2021-02-13 21:22

    It's important to look at this from a language lawyer perspective.

    It's basically impossible for an implementation to implement mutex, condition variables, and the like in a way that leaves them trivially copyable. At some point, you have to write a destructor, and that destructor is very likely going to have to do non-trivial work.

    But that doesn't matter. Why? Because the standard does not explicitly state that such types will not be trivially copyable. And therefore it is, from the perspective of the standard, theoretically possible for such objects to be trivially copyable.

    Even though no functional implementation every can be, the point of N4460 is to make it very clear that such types will never be trivially copyable.

    0 讨论(0)
提交回复
热议问题