【问题标题】:Will memcpy or memmove cause problems copying classes?memcpy 或 memmove 会导致复制类出现问题吗?
【发布时间】:2012-08-21 13:47:10
【问题描述】:

假设我有任何类型的类或结构。没有虚函数或任何东西,只有一些自定义构造函数,以及一些需要在析构函数中清理的指针。

在这个结构上使用 memcpy 或 memmove 会有任何不利影响吗?删除移动的结构会导致问题吗?该问题假设内存对齐也是正确的,并且我们正在复制到安全内存。

【问题讨论】:

  • 自然的问题是为什么
  • 同意@JonathonReinhart 为什么不创建一个新的。原因很简单,它将使代码在 X 年内更具可读性
  • 让我猜猜。你只是想知道?简短的回答,不要。实现像.NET这样的克隆函数
  • @ColeJohnson 我认为clone 在 .NET 之前就存在 :-)
  • @AdrianCornish 我知道它以前存在,但作为一个 C# 人,我只想到 .NET 而不是其他语言

标签: c++ class memcpy memmove


【解决方案1】:

在一般情况下,是的,会有问题。 memcpymemmove 都是按位运算,没有进一步的语义。这可能不足以移动对象*,而复制显然也不够。

在复制的情况下,它会中断,因为多个对象将引用同一个动态分配的内存,并且多个析构函数会尝试释放它。请注意,shared_ptr 之类的解决方案在这里无济于事,因为共享所有权是memcpy/memmove 不提供的进一步语义的一部分。

对于移动,根据类型,在某些情况下您可能会侥幸逃脱。但如果对象持有指向被移动元素的指针/引用(包括自引用),则它将不起作用,因为指针将按位复制(同样,复制/移动没有进一步的语义)和将引用旧位置。

一般答案还是一样:不要


* 不要将 move 放在完全 C++11 的意义上。我已经看到一个标准库容器的实现,它使用特殊标签来启用 moving 对象,同时通过使用memcpy 来增加缓冲区,但它需要在存​​储类型中显式注释,将对象标记为通过memcpy安全地移动,在对象被放入新缓冲区后,旧缓冲区被丢弃没有调用任何析构函数(C++11 move em> 需要让对象处于 可破坏 状态,这无法通过此 hack 实现)

【讨论】:

    【解决方案2】:

    通常在基于类的对象上使用 memcpy 不是一个好主意。最可能的问题是复制指针然后删除它。您应该改用复制构造函数或赋值运算符。

    【讨论】:

    • 我同意使用memcpy 不是一个好主意的观点。重新“在对象上”,注意memcpy 只能用于对象。所以有点多余。
    • 如果您认为原语是对象,那么是的,该陈述是正确的。
    • 这个问题是关于 C++ 的,C++ 标准定义了object 是什么——没有个人意见的余地。看看这个。并且请不要在回答 C++ 问题时使用 Java 术语(或任何术语)
    • 供参考:1.8 C++ 对象模型“对象是一个存储区域。”通过原语,我的意思是标量变量,根据定义实际上是对象。我已经编辑了原始帖子以表达我对基于类的对象的初衷。虽然看起来,维基百科需要一些编辑,因为它说,“对象是在运行时创建的类的实例”
    • +1 用于研究。 :-) 好吧,它还不完美,但没有什么是完美的。基于类的对象包括 POD 对象(如简单的 C struct);您可能指的是非 POD 对象。或对应的 C++11 更精炼的术语。它变得复杂......
    【解决方案3】:

    不,不要这样做。

    如果您memcpy 一个结构的析构函数删除了自身内部的指针,那么当结构的第二个实例以任何方式被破坏时,您最终会执行双重删除。

    C++ 习惯用法是类的复制构造函数,std::copy 或其任何朋友用于复制范围/序列/容器。

    【讨论】:

      【解决方案4】:

      如果您使用的是 C++11,则可以使用 std::is_trivially_copyable 来确定是否可以使用 memcpymemmove 复制或移动对象。来自文档:

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

      许多课程不符合此描述,您必须注意课程可能会发生变化。我建议,如果你打算在 C++ 对象上使用memcpy/memmove,你应该以某种方式保护不需要的使用。例如,如果你正在实现一个容器类,那么容器所拥有的类型很容易被修改,这样它就不再是可复制的(例如,有人添加了一个虚函数)。您可以使用static_assert

      template<typename T>
      class MemcopyableArray
      {
          static_assert(std::is_trivially_copyable<T>::value, "MemcopyableArray used with object type that is not trivially copyable.");
          // ...
      };
      

      【讨论】:

        【解决方案5】:

        除了安全性(正如其他答案已经指出的那样是最重要的问题)之外,还可能存在性能问题,尤其是对于小型物体。

        即使对于简单的 POD 类型,您也可能会发现在复制构造函数的初始化程序列表中进行适当的初始化(或根据您的使用情况在赋值运算符中进行赋值)实际上甚至比 memcpy 的内部版本更快。这很可能是由于 memcpy 的入口代码可能会检查字对齐、重叠、缓冲区/内存访问权限等。在 Visual C++ 10.0 及更高版本中,举个具体的例子,你会惊讶于许多测试各种事物的序言代码在 memcpy 甚至开始其逻辑功能之前执行。

        【讨论】:

        • 我也是,直到我真正分析了它。对于较大的对象来说它非常快,这是一个非常特定于平台的概念,但对于
        • +1 表示效率。指出效率应该排在第三位或更低,这很好,但事实是,不仅大多数初学者,而且大多数程序员(错误地)首先考虑效率。然后,对关于什么是有效的什么不是有效的通常不正确的想法进行一些纠正确实很有帮助。让我补充一点,如果人们选择在效率问题上花费大量时间,那么衡量也是一个好主意。你也提到了。 :-)
        • 这是一个很好的例子,说明性能的最大敌人是假设。无论您认为自己知道什么,衡量都是唯一的真理。
        猜你喜欢
        • 2014-12-26
        • 1970-01-01
        • 2016-06-16
        • 2011-05-23
        • 1970-01-01
        • 1970-01-01
        • 2018-11-21
        • 1970-01-01
        相关资源
        最近更新 更多