Почему мьютексы и переменные условия тривиально копируются?

LWG 2424 обсуждает нежелательный статус атомистики, мьютексов и переменных условий как тривиально скопируемых в C + +14. Я понимаю, что исправление уже выстроено в линию, но std::mutex, std::condition variable и др. как представляется, имеют нетривиальные деструкторы. Например:

30.4.1.2.1 Мьютекс класса [thread.mutex.class]

namespace std {
  class mutex {
  public:
    constexpr mutex() noexcept;
    ~mutex(); // user-provided => non-trivial

    …
  }
}

Не следует ли это дисквалифицировать их как тривиально скопируемые?

Ответ 1

Либо это была моя ошибка, либо я был неправильно истолкован, и я честно не помню, какой из них.

Однако у меня есть очень прочные советы по этому вопросу:

Не используйте is_trivial и is_trivially_copyable! EVER!!!

Вместо этого используйте один из них:

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>

Обоснование:

TL;DR: См. этот отличный вопрос и правильный ответ.

Никто (включая меня) не может запомнить определение is_trivial и is_trivially_copyable. И если вы действительно посмотрите его, а затем потратите 10 минут на его анализ, он может или не может делать то, что вы интуитивно думаете. И если вам удастся его правильно проанализировать, CWG может изменить свое определение с небольшим или отсутствующим уведомлением и аннулировать ваш код.

Использование is_trivial и is_trivially_copyable воспроизводится с огнем.

Однако эти:

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>

делают именно то, что они звучат так, как они, и вряд ли когда-либо изменит свое определение. Может показаться чересчур многословным иметь дело с каждым из специальных членов по отдельности. Но это окупится в стабильности/надежности вашего кода. И если вам нужно, упакуйте эти индивидуальные черты в пользовательский признак.

Обновление

Например, clang и gcc компилируют эту программу:

#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>();
}

Обратите внимание, что X тривиально скопируема, но не тривиально копируется. Насколько мне известно, это соответствует поведению.

В VS-2015 в настоящее время говорится, что X ни тривиально не копируется, ни тривиально копируется конструктивно. Я считаю, что это неправильно в соответствии с текущей спецификацией, но это точно соответствует моему здравому смыслу.

Если мне понадобилось memcpy для неинициализированной памяти, я бы доверял is_trivially_copy_constructible над is_trivially_copyable, чтобы убедить меня, что такая операция будет в порядке. Если бы я хотел memcpy инициализировать память, я бы проверил is_trivially_copy_assignable.

Ответ 2

Не все реализации предоставляют нетривиальный деструктор для mutex. См. LibstdС++ (и предположим, что __GTHREAD_MUTEX_INIT определено):

  // 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;
  };

Эта реализация mutex является стандартной и тривиально копируемой (которая может быть проверена через Coliru). Точно так же ничто не останавливает реализацию от сохранения condition_variable тривиально разрушаемого (см. [thread.condition.condvar]/6, хотя я не смог найти реализацию, которая делает).

Суть в том, что нам нужны четкие, нормативные гарантии, а не умные, тонкие интерпретации того, что condition_variable делать или не делать (и как это должно быть выполнено).

Ответ 3

Важно взглянуть на это с точки зрения адвоката языка.

В принципе невозможно реализовать реализацию mutex, переменных условий и т.д. таким образом, чтобы они были тривиально скопируемыми. В какой-то момент вам нужно написать деструктор, и этот деструктор, скорее всего, придется выполнять нетривиальную работу.

Но это не имеет значения. Зачем? Поскольку в стандарте явно не указано, что такие типы не будут тривиально скопируемыми. И поэтому, с точки зрения стандарта, теоретически возможно, чтобы такие объекты были тривиально скопируемыми.

Несмотря на то, что никакой функциональной реализации не может быть, точка N4460 заключается в том, чтобы четко указать, что такие типы никогда не будут тривиально скопируемыми.