管理琐碎的类型

jot*_*tik 17 c++ language-lawyer c++11 c++14 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保持,代码是否在代码跳过Tat p(即使用T * tPtr = new (p) T();)的初始化之前调用未定义的行为,否则访问*pTT * tPtr = static_cast<T *>(p);在这种情况下,是否可以使用而不用担心未定义的行为?
  2. 如果std::is_trivially_destructible<T>::value成立,是否跳过Tat *p(即通过调用tPtr->~T();)的破坏导致未定义的行为?
  3. 对于任何类型Ustd::is_trivially_assignable<T, U>::value保持,std::memcpy(&t, &u, sizeof(U));等同于t = std::forward<U>(u);(对于任何t类型Tu类型U)或它是否会导致未定义的行为?

Col*_*mbo 10

  1. 不,你不能.T该存储中没有类型的对象,并且访问存储,就好像存在未定义的那样.又见TC的答案在这里.

    只是为了澄清[basic.life]/1中的措辞,它表示具有空初始化的对象从存储分配开始就是活着的:该措辞显然是指对象的初始化.当用operator new或分配原始存储时,没有对象的初始化是空的malloc,因此我们不能认为"它"是活的,因为"它"不存在.事实上,在分配存储之后但在空白初始化发生之前(即遇到它们的定义),只能访问由具有空初始化的定义创建的对象.

  2. 省略析构函数调用永远不会导致未定义的行为.然而,在例如模板中尝试在该区域中进行任何优化是没有意义的,因为一个简单的析构函数被优化掉了.

  3. 现在,要求是可以轻易复制的,类型必须匹配.但是,这可能过于严格.Dos Reis的N3751至少提出了不同类型的工作方式,我可以想象这个规则将在未来扩展到一种类型的普通拷贝分配.

    但是,你具体展示的内容并没有多大意义(尤其是因为你要求分配给标量xvalue,这是一个错误的形式),因为琐碎的赋值可以保存在其实际不是"赋值"的类型之间.琐碎的",即具有与...相同的语义memcpy.例如is_trivially_assignable<int&, double>,并不意味着可以通过复制对象表示将一个"分配"给另一个.

  • @Msalters喜欢与否,但当前的C++标准使得`*(int*)malloc(sizeof(int))= 42`非法.这对我来说似乎是一个可怕的想法,但我不喜欢标准所说的并不会改变标准所说的内容.它不是数千行 - 由于这些规则而导致许多**行**代码被破坏.在没有巨大C遗产的小型新项目中,它们可能是合理的 - 但是人们认为这是标准的合理位置,这简直是荒谬的. (2认同)

luk*_*k32 5

  1. 从技术上讲,重新解释存储不足以引入新对象。查看有关默认构造函数状态的注释:

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

但说明中说这是一个正式的限制,因此在许多情况下可能是安全的。虽然不能保证。

  1. is_assignable甚至不保证分配将是法律在一定条件下:

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

您所描述的看起来更像是is_trivially_copyable,它说:

普通可复制类型的对象是唯一可以使用std :: memcpy安全复制或可以使用std :: ofstream :: write()/ std :: ifstream :: read()从二进制文件串行化到二进制文件中的C ++对象

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