为什么互斥量和条件变量可以轻易复制?

时间:2016-03-22 16:44:32

标签: c++ language-lawyer c++14

LWG 2424在C ++ 14中讨论了原子,互斥和条件变量的不良状态trivially copyable。我感谢我的解决方法是already lined up,但std::mutexstd::condition variable等。似乎有非平凡的析构函数。例如:

  

30.4.1.2.1类互斥[thread.mutex.class]

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

    …
  }
}

这不应该取消他们的资格吗?

3 个答案:

答案 0 :(得分:8)

要么是我的错误,要么我被误引,我老实说不记得是哪一个。

但是,我对这个主题有非常强烈的建议:

  

请勿使用is_trivialis_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>

理由:

tldr:请参阅此excellent question and correct answer.

没有人(包括我自己)可以记住is_trivialis_trivially_copyable的定义。如果你确实碰巧查了一下,然后花了10分钟对它进行分析,它可能会或可能不会做你直觉认为它做的事情。如果您设法正确分析,CWG可能会在很少或没有通知的情况下更改其定义并使您的代码无效。

使用is_trivialis_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&amp; 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

答案 1 :(得分:4)

并非所有实现都为mutex提供了一个重要的析构函数。请参阅libstdc ++(并假设已定义__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的这种实现既符合标准,也可以轻松复制(可以验证via Coliru)。同样,没有什么能阻止实现保持condition_variable可以轻易破坏(参见[thread.condition.condvar]/6,尽管我找不到一个实现)。

最重要的是,我们需要明确的,规范性的保证,而不是对condition_variable做或不做(以及如何做到这一点)的巧妙,微妙的解释。

答案 2 :(得分:3)

从语言律师的角度来看这一点非常重要。

实现基本上不可能实现mutex,条件变量等,使它们可以轻易地复制。在某些时候,你必须编写一个析构函数,而析构函数很可能不得不做一些非平凡的工作。

但这并不重要。为什么?因为标准没有明确声明这些类型不会轻易复制。因此,从标准的角度来看,理论上可能这些对象可以轻易地复制。

即使没有任何功能实现,N4460的要点是要明确表示这些类型永远不会轻易复制。