【问题标题】:When is a type in c++11 allowed to be memcpyed?什么时候允许对 c++11 中的类型进行 memcpyed?
【发布时间】:2014-11-19 04:49:42
【问题描述】:

我的问题如下:

如果我想复制一个类类型,memcpy 可以很快完成。这在某些情况下是允许的。

我们有一些类型特征:

  • is_standard_layout。
  • is_trivially_copyable。

我想知道的是当类型“可按位复制”时的确切要求。

我的结论是,如果 is_trivally_copyableis_standard_layout 特征都为真,则类型是可按位复制的:

  1. 这正是我需要按位复制的吗?
  2. 是否过度约束?
  3. 约束不足吗?

P.S.:当然,memcpy 的结果一定是正确的。我知道我可以在任何情况下进行 memcpy,但不正确。

【问题讨论】:

  • 赋值运算符的复制速度也一样快,你知道的。作为奖励,如果您将您的课程更改为非平凡可复制的,它仍然有效。
  • 您是否真的需要使用memcpy 以除速度之外的某些特定原因?因为只需使用默认的复制 ctor/赋值运算符,您就可以从编译器获得相同的速度,同时保持类型安全。对于普通可复制类型,即使是非常高级别的东西,例如在普通可复制类型的数组上的 std::copy,也会被优化为单个 memcpy
  • 只是为了了解内部工作原理。
  • 默认副本比memcpy function是很常见的,因为它可以跳过所有类型的错位检查。这就是为什么编译器经常将memcpy 实现为内在函数,以使其与默认的复制构造函数一样快。
  • @NicuStiurca:我来到这里是因为我需要一个用于存放任意此类数据的容器。通过使用 enabled if,我可以阻止人们构建无法以微不足道的方式复制的消息。

标签: c++ c++11 memory


【解决方案1】:

is_trivially_copyable<T>::value 为真时,您可以使用memcpy 复制T 类型的对象。没有特别需要该类型是标准布局类型。 “可简单复制”的定义本质上是这样做是安全的。

使用memcpy 可以安全复制但不是标准布局的类示例:

struct T {
  int i;
private:
  int j;
};

因为这个类对不同的非静态数据成员使用不同的访问控制,所以它不是标准的布局,但它仍然可以很容易地复制。

【讨论】:

  • 如果一个类型是可简单复制的,它总是标准布局?
  • @GermánDiago 不,您可以拥有非标准布局的普通可复制类型。
【解决方案2】:

如果is_trivally_copyable<T>::value(或C++14 中的is_trivially_copyable<T>(),或C++17 中的is_trivially_copyable_v<T>)不为零,则可以使用memcpy 复制该类型。

根据 C++ 标准,可简单复制的类型意味着:

构成对象的底层字节可以复制到数组中 字符或无符号字符。如果 char 或 unsigned char 数组的内容被复制回 对象,该对象随后应保持其原始值。

但是,重要的是要意识到指针也是可简单复制的类型。每当您要复制的数据结构中存在指针时,您必须在头脑中确保复制它们是正确的。

仅依赖于可简单复制的对象可能导致危险的示例:

  • 一种树结构实现,其中您的数据被放置在内存的连续区域中,但节点将绝对地址存储到子节点
  • 为了多线程性能(以减少缓存崩溃)而创建某些数据的多个实例,并在内部拥有指针,指向任何地方
  • 您有一个没有指针的平面对象,但内部嵌入了第三方结构。第三方结构在未来的某个时间点包含一个不应存在两次或更多次的指针。

因此,无论何时进行内存复制,请记住检查是否可以在特定情况下复制指针,以及是否可以。

意识到is_trivially_copyable 只是编译器用语中的“语法检查”,而不是“语义测试”

【讨论】:

  • 请注意,您的警告只是关于复制内容; “琐碎”是一条红鲱鱼。
  • @Hurkyl:恐怕我不确定你的目标是什么?
【解决方案3】:

来自http://en.cppreference.com/w/cpp/types/is_trivially_copyable

普通可复制类型的对象是唯一可以使用std::memcpy 安全复制或使用std::ofstream::write()/std::ifstream::read() 序列化到二进制文件/从二进制文件序列化的C++ 对象。通常,可简单复制的类型是可以将基础字节复制到 char 或 unsigned char 数组以及相同类型的新对象的任何类型,并且生成的对象将具有与原始对象相同的值。

【讨论】:

    【解决方案4】:

    具有平凡的复制构造函数、平凡的复制赋值运算符和 可以使用memcpymemmove 复制琐碎的析构函数

    T 的特殊成员函数的要求是微不足道的

    复制构造函数 (cc) 和复制赋值运算符 (ca)

    • 不是用户提供的(意思是隐式定义或默认),如果是默认的,其签名与隐式定义相同
    • T 没有虚拟成员函数
    • T 没有虚拟基类
    • T 的每个直接基数选择的 cc/ca 是微不足道的
    • T 的每个非静态类类型(或类类型数组)成员选择的 cc/ca 是微不足道的
    • T 没有 volatile 限定类型的非静态数据成员(C++14 起)

    析构函数

    • 不是用户提供的(意味着它是隐式定义或默认的)
    • 不是虚拟的(即基类析构函数不是虚拟的)
    • 所有直接基类都有微不足道的析构函数
    • 类类型的所有非静态数据成员(或类类型的数组)都有普通的析构函数

    仅仅将函数声明为= default 并不能让它变得微不足道(只有在 该类还支持相应函数的所有其他标准) 但是在用户代码中显式编写函数确实可以防止它变得微不足道。此外,与 C 语言兼容的所有数据类型(POD 类型)可轻松复制

    来源:C++ Concurrency in action 和 cppreference.com

    【讨论】:

    • 为什么需要一个简单的析构函数来memcpy?
    • @GermánDiago 打败了我……只是引用“C++ Concurrency in action” 其余的证据暗示不需要它(太糟糕了,我找不到实现 std::is_trivially_copyable 的编译器)
    • 所以这意味着 trivially_destructible 不是必需的,对吧?这就是你的意思?
    • @GermánDiago 我尝试编写一个仅实现析构函数的结构(使其变得不平凡),而在实现std::is_trivially_copyable 的 VS12 中,我得到了否定的响应。如果 VS 和 Antony Williams 是对的,那么这是一个要求,我猜它与类布局、无保证、类似 C 的行为以及类似的东西有关。虽然没有在线 VS 编译器来显示这一点
    • @GermánDiago:如果析构函数不是微不足道的,那么您没有正确销毁目标位置存在的对象(如果存在)(也就是说,您不是 memcpying 到 @ 987654333@ 数组来创建新的“对象”)。
    【解决方案5】:

    我的理解是

    • 一个对象应该有默认的构造函数/destructor。
    • 默认复制和移动操作。
    • 没有静态和虚函数有多个
    • 非静态数据成员的访问说明符防止重要
    • 布局优化具有非静态成员或非静态成员 标准布局。

    您可以使用标准函数 is_pod::value 测试给定类型是否为 pod (Plain Old Data)

    参考:C++ 编程语言第 4 版

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-08-29
      • 1970-01-01
      • 2016-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-11
      • 1970-01-01
      相关资源
      最近更新 更多