LWG 2424在C ++ 14中讨论了原子,互斥和条件变量的不良状态trivially copyable。我感谢我的解决方法是already lined up,但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 … } }
这不应该取消他们的资格吗?
答案 0 :(得分:8)
要么是我的错误,要么我被误引,我老实说不记得是哪一个。
但是,我对这个主题有非常强烈的建议:
请勿使用
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:请参阅此excellent question and correct answer.
没有人(包括我自己)可以记住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&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的要点是要明确表示这些类型永远不会轻易复制。