【问题标题】:Is memcpy with this pointer safe?带有这个指针的 memcpy 安全吗?
【发布时间】:2018-11-27 04:04:50
【问题描述】:

我目前正在用 C++ 编写自己的字符串实现。 (只是为了锻炼)。

但是,我目前有这个复制构造函数:

// "obj" has the same type of *this, it's just another string object
string_base<T>(const string_base<T> &obj)
        : len(obj.length()), cap(obj.capacity()) {
    raw_data = new T[cap];
    for (unsigned i = 0; i < cap; i++)
        raw_data[i] = obj.data()[i];
    raw_data[len] = 0x00;
}

我想稍微提高性能。所以我想到了使用memcpy()obj 复制到*this 的想法。

就这样:

// "obj" has the same type of *this, it's just another string object
string_base<T>(const string_base<T> &obj) {
     memcpy(this, &obj, sizeof(string_base<T>));
}

这样覆盖*this的数据安全吗?或者这会产生什么问题?

提前致谢!

【问题讨论】:

  • 这个帖子读起来很有趣;几乎重复:stackoverflow.com/questions/30114397/…
  • 你也可以使用在 NULL 字符处停止的 strcpy。与 memcpy 相比,它可能在性能方面有所不同
  • 要考虑的另一件事是,现代编译器通常足够聪明,可以识别第一个示例中的循环,并在适当的时候将其替换为 memcpy。所以这可能是一个过早的优化。
  • 顺便说一句,您只需要复制 len 对象,而不是 cap

标签: c++ arrays memcpy this-pointer


【解决方案1】:

不,这不安全。来自 cppreference.com:

如果对象不是TriviallyCopyable,则memcpy 的行为未指定且可能未定义。

你的类不是TriviallyCopyable,因为它的拷贝构造函数是用户提供的。


此外,您的复制构造函数只会制作浅拷贝(如果您愿意,这可能没问题,例如,将写入时复制机制应用于您的字符串)。

【讨论】:

  • “TriviallyCopyable”是什么意思?
  • 我不确定是否可以简单地复制就足够了,尽管这是必要的。
  • @codekaizer 一点也不:std::cout &lt;&lt; std::is_trivially_copyable&lt;std::string&gt;::value &lt;&lt; std::endl;。 (同样,std::string 具有用户定义的复制构造函数。)
  • @DanielLangr,是的,刚刚阅读了this answer。根据评论 - std::string 在 c++14 及更高版本中不可轻易复制
  • @codekaizer 不,请阅读下一条评论:“包含std::string 的对象从未被轻易复制”
【解决方案2】:

产生问题。所有的引用和指针都将被复制,甚至是指向 raw_data 的指针,它与源对象相同。

作为使用 memcpy 的必要条件,你的班级应该:

  • 成为Trivially Copyable
  • 没有引用或指针,除非:静态指针或不拥有指向的数据。除非您知道自己在做什么,例如实现写入时复制机制,或者这种行为是有意和管理的。

【讨论】:

  • 指针如果不拥有它们所指向的数据,则可以。否则,我们会多次删除同一内存。
  • 正是...@Aconcagua 所说的。已编辑。
  • 当然,它们也可能被引用计数。至于写时复制机制。这就是为什么我会谨慎对待如此强烈的声明。
  • 好吧,我再次编辑了,但应该是这样。必须注意引用和指针
  • 如果你有引用成员,并且你做了一个memcpy,这意味着你正在重新绑定一个引用。这是非法的。
【解决方案3】:

正如其他人所说,为了让memcpy 正常工作,被复制的对象必须是可简单复制的。对于模板中的T 之类的任意类型,您无法确定这一点。当然,您可以检查它,但让其他人进行检查要容易得多。不要编写该循环并对其进行调整,而是使用std::copy_n。它会在合适的时候使用memcpy,在不合适的时候使用逐个元素的复制。所以改变

raw_data = new T[cap];
for (unsigned i = 0; i < cap; i++)
    raw_data[i] = obj.data()[i];

raw_data = new T[cap];
std::copy_n(obj.data(), cap, raw_data);

这还有一点优势,即在每次通过循环时不评估 obj.data(),这是您的编译器可能会或可能不会应用的优化。

【讨论】:

    【解决方案4】:

    从有限的片段中可以明显看出raw_data 是一个成员,并且是指向Tnew[]'ed 数组的指针。如果你 memcpy 对象,你 memcpy 这个指针。你不要复制数组。

    看看你的析构函数。它可能无条件地调用delete[]。它不知道存在多少副本。这意味着它过于频繁地调用delete[]。这是可以修复的:你需要类似于shared_ptr 的东西。这一点都不简单。您必须担心该共享计数的线程安全性。显然你不能只 memcpy 对象,因为这不会更新共享计数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-24
      • 2013-02-15
      • 2014-12-15
      • 2016-09-02
      相关资源
      最近更新 更多