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


LWG 2424 讨论了原子,互斥和条件变量的不良状态 平凡的可复制 在C ++中14。我很感激修复 已经排队了但是 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

    …
  }
}

这不应该取消他们可以轻易复制的资格吗?


12826
now


起源

看起来这只是霍华德在讨论中所说的,可能没有考虑过它? - SergeyA
你确定 std::mutex 是否可以复制?根据标准,他们复制构造函数,复制赋值都标记为 delete。 - NathanOliver
@NathanOliver这是LWG 1734的要点。(虽然你是对的 - mutex 不是轻易复制的。因为它的析构函数不是微不足道的。) - Columbo
@NathanOliver:删除的特殊成员函数在技术上仍然是一个微不足道的功能。 - Nicol Bolas
@NathanOliver海湾合作委员会似乎认为就是这样。 - Joseph Thomson


答案:


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

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

不使用 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>

理由:

tldr:  看到这个 优秀的问题和正确的答案。

没有人(包括我自己)能记住这个定义 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


7
2018-03-22 20:41



从马的嘴里!感谢您的建议。我没有意识到这些特征是在C ++ 17中。为了清楚起见,您认为标准是强制要求用户提供的析构函数 std::mutex 和公司?我问,因为其他答案似乎并不认为是这种情况。 - Joseph Thomson
是的,但这是标准中的模糊区域。互斥规范并没有考虑到关于“琐碎”的规则。这种特质可能会改变。但总的来说,我并不认为规范中关于互斥体是否可以轻易复制的重要性,因为“平凡可复制”的定义既奇特又容易改变。更重要的是这样的问题:mutex可以简单地复制构造吗? mutex可以简单地复制分配吗?即使答案不可移植,后面的这些问题也可以被轻易询问并清楚地回答,从而产生可移植的代码。 - Howard Hinnant
到底是什么 is_trivially_copy_constructible 对...有用?它本身不保证按字节顺序复制。事实上,建议 is_trivially_copy_constructible 代替 is_trivially_copyable 可能是危险的,因为人们可以(虚假地)推断出这一点 memcpy如果复制构造函数很简单,那就很好。 - Columbo
@Columbo:我试图用一个例子来澄清。 - Howard Hinnant
我喜欢用 test() 作为在类中定义的私有静态成员函数(我称之为 assert_traits() 通常),这样我就可以在定义的角度看待类语义。该 static_asserts 因为一个类在成员定义中被认为是完整的。 - TemplateRex


并非所有实现都提供了一个非常重要的析构函数 mutex。请参阅libstdc ++(并假设