C++ 为什么互斥量和条件变量是可复制的?

C++ 为什么互斥量和条件变量是可复制的?,c++,language-lawyer,c++14,C++,Language Lawyer,C++14,讨论原子、互斥体和条件变量在C++14中的不良状态。我很欣赏修复,但是,等等,似乎有非平凡的析构函数。例如: 30.4.1.2.1类互斥体[thread.mutex.Class] namespace std { class mutex { public: constexpr mutex() noexcept; ~mutex(); // user-provided => non-trivial … } } 这难道不应该取消它们的可复制性吗?从语言律师的

讨论原子、互斥体和条件变量在C++14中的不良状态。我很欣赏修复,但是,等等,似乎有非平凡的析构函数。例如:

30.4.1.2.1类互斥体[thread.mutex.Class]

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

    …
  }
}

这难道不应该取消它们的可复制性吗?

从语言律师的角度来看这一点很重要

对于一个实现来说,基本上不可能实现互斥体、条件变量等等,从而使它们可以简单地复制。在某个时刻,您必须编写一个析构函数,并且该析构函数很可能必须执行非平凡的工作

但这并不重要。为什么?因为该标准没有明确规定此类类型不可复制。因此,从标准的角度来看,从理论上讲,这些对象是可以复制的


即使没有任何功能实现是可以实现的,N4460的要点是非常清楚地表明,这样的类型永远不会是可复制的。

并非所有的实现都为
互斥体提供了一个非平凡的析构函数。请参阅libstdc++(并假设已定义了
\ugthread\umutex\uinit
):

互斥体的这种实现既符合标准,又可以复制(可以验证)。类似地,没有任何东西可以阻止实现将
条件_变量
保持为可破坏的(参见,尽管我找不到这样的实现)


归根结底,我们需要明确、规范的保证,而不是对条件变量做什么或不做什么(以及如何做到这一点)的聪明、微妙的解释。

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

然而,我在这个问题上有一个非常强烈的建议:

不要使用
是琐碎的
,也不要使用
是琐碎的可复制的
!永远

相反,请使用以下选项之一:

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>();
}
#包括
模板
无效的
测试()
{
使用名称空间std;
静态断言(!is_平凡{},”);
静态断言(是可复制的{},”);
静态断言(是可破坏的{},”);
静态断言(是可破坏的{},”);
静态断言(!是可构造的{},”);
静态断言(!是可构造的{},”);
静态断言(是可分配的{},”);
静态断言(!是平凡的移动可构造的{},”);
静态断言(是平凡的移动可赋值的{},”);
}
结构X
{
X(常数X&)=删除;
};
int
main()
{
test();
}
请注意,
X
是可复制的,但不是可复制的。就我所知,这是一种顺从的行为

VS-2015目前表示,
X
既不是可复制的,也不是可复制的。我相信这是错误的,根据目前的规范,但它肯定符合我的常识告诉我


如果我需要
memcpy
到未初始化的内存,我会相信
is\u琐碎地\u copy\u constructible
over
is\u琐碎地\u copyable
来确保这样的操作是可以的。如果我想
memcpy
初始化内存,我会检查
is\u minality\u copy\u assignable

看起来这只是霍华德在讨论中说的话,可能没有仔细考虑过?你确定
std::mutex
是可以复制的吗?根据标准,他们复制构造函数和复制分配都标记为
delete
@NathanOliver,这是LWG 1734的要点。(虽然你是对的-
mutex
不容易复制。因为它的析构函数不容易复制。)@NathanOliver:一个被删除的特殊成员函数在技术上仍然是一个很容易复制的函数。@NathanOliver GCC似乎认为是这样。我不确定他们在这里是否采取了正确的方法。在我看来,正确的方法是将具有已删除赋值运算符/复制运算符的类型视为非平凡的可复制类型。是的,它破坏了一些向后兼容性,但这是正确的。如果某些东西不能用
操作符=
复制,为什么用
memcpy()
复制是合法的?但是标准没有为这些类型指定用户提供的析构函数吗?@JosephThomson:它指定有一个析构函数;类型必须是
Destructibe
。但是标准中没有要求析构函数是非平凡的。再次,像语言律师一样思考;“没有规则”,所以这是可能的。@SergeyA:“是的,它破坏了一些向后兼容性”就委员会而言,就这样结束了。普通可复制规则和关于默认/删除成员函数的各种规则并不是为了改变现有行为。@JosephThomson,没有“普通可移动”这样的东西。因此,要分析可移动对象,需要检查平凡的移动构造函数指定析构函数不应为默认值,或者这只是类接口的一个示例?已编辑此问题以说明我可能误解的根源。@JosephThomson不,这不是暗示。看,从马嘴里!谢谢你的建议。我不知道
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>
#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>();
}