管理琐碎的类型

时间:2017-02-17 09:42:46

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

我发现C ++中的琐碎类型的复杂性非常重要,需要了解并希望有人能够启发我以下内容。

给定T类型,使用T::operator new(std::size_t)::operator new[](std::size_t)分配std::aligned_storage的存储空间,以及指向某个位置的void * p该存储空间适合于T,以便它可以在p构建:

  1. 如果std::is_trivially_default_constructible<T>::value成立,则代码在T p初始化T * tPtr = new (p) T();时(即使用*p)调用未定义的行为,否则访问T作为T * tPtr = static_cast<T *>(p);?在这种情况下,可以只使用std::is_trivially_destructible<T>::value而不用担心未定义的行为吗?
  2. 如果T成立,是否在*p跳过tPtr->~T();的销毁(即通过调用U)导致未定义的行为?
  3. 对于std::is_trivially_assignable<T, U>::value所持有的任何类型std::memcpy(&t, &u, sizeof(U));t = std::forward<U>(u);等同于t(适用于T类型的任何u和{类型为U的{​​1}}或会导致未定义的行为吗?

2 个答案:

答案 0 :(得分:10)

  1. 不,你不能。该存储中没有T类型的对象,并且像访问is undefined一样访问存储。另见T.C。的回答here

    只是为了澄清[basic.life]/1中的措辞,其中说明具有空初始化的对象从存储分配开始是活着的:该措辞显然是指对象的初始化 。在使用operator newmalloc分配原始存储时,没有任何对象的初始化是空的,因此我们无法考虑&#34;它&#34;活着,因为&#34;它&#34;不存在。事实上,只有在具有空白初始化的定义创建的对象可以在分配存储之后但在空白初始化之前(即遇到它们的定义)时被访问。

  2. 省略析构函数调用永远不会导致未定义的行为。但是,尝试在这方面进行任何优化都是毫无意义的。模板,因为一个简单的析构函数被优化掉了。

  3. 目前,要求为being trivially copyable,且类型必须匹配。但是,这可能过于严格。 Dos Reis's N3751至少提出了不同类型的工作方式,我可以想象这个规则将在未来扩展到一种类型的普通副本分配。

    然而,你所特别展示的内容并没有多大意义(尤其是因为你要求分配给标量xvalue,这是一个错误的形式),因为琐碎的赋值可以保持不变其分配实际上不是&#34;平凡的#34;,即具有与memcpy相同的语义。例如。 is_trivially_assignable<int&, double>并不意味着可以分配一个&#34;通过复制对象表示来到另一个。

答案 1 :(得分:4)

  1. 技术上重新解释存储不足以引入新对象。请查看Trivial default constructor州的说明:
  2.   

    普通的默认构造函数是不执行任何操作的构造函数。与C语言兼容的所有数据类型(POD类型)都是可以默认构造的。但是,与C不同,只需重新解释适当对齐的存储,例如使用std :: malloc分配的内存,就无法创建具有普通默认构造函数的对象:placement-new需要正式引入新对象并避免潜在的未定义行为

    但是说明这是一个正式的限制,所以在许多情况下它可能是安全的。但不保证。

    1. 否。 is_assignable甚至不保证在某些条件下转让是合法的:
    2.   

      此特征不检查赋值表达式的直接上下文之外的任何内容:如果使用T或U将触发模板特化,生成隐式定义的特殊成员函数等,并且那些有错误,实际即使std :: is_assignable :: value编译并计算为true,也可能无法编译赋值。

      您所描述的内容更像is_trivially_copyable,其中包含:

        

      可复制类型的对象是唯一可以使用std :: memcpy 安全复制或使用std :: ofstream :: write()/ std序列化到/来自二进制文件的C ++对象:: ifstream的::读()。

      1. 我真的不知道。我相信KerrekSB的评论。