这个指针的memcpy操作安全吗?

3

我目前正在用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 的数据是安全的吗?或者会产生任何问题吗?

提前感谢!


2
这个线程非常有趣;几乎是一个重复的问题:https://dev59.com/V10a5IYBdhLWcg3woJ55 - Bathsheba
你也可以使用strcpy,它会在空字符处停止。与memcpy相比,它的性能可能有所不同。 - MauriceRandomNumber
另一个需要考虑的因素是,现代编译器通常足够智能,能够识别第一个示例中的循环,并在适当时将其替换为memcpy。因此,这可能是一种过早的优化。 - Bo Persson
顺便说一下,您只需要复制“len”对象,而不是“cap”。 - Jarod42
4个回答

3
不,不安全。来自cppreference.com的解释:
如果对象不是“TriviallyCopyable”,则memcpy的行为未指定,可能是未定义的。
由于您的类具有用户提供的复制构造函数,因此它不是“TriviallyCopyable”。
此外,您的复制构造函数将仅进行浅层拷贝(如果您想要使用字符串的写时复制机制,则这可能没有问题)。

“TriviallyCopyable” 是什么意思? - cocoz1
1
我不确定仅仅是可平凡复制就足够了,尽管它是必要的。 - Bathsheba
1
@codekaizer 没有问题:std::cout << std :: is_trivially_copyable <std :: string> :: value << std :: endl;。(再次说明,std :: string 具有用户定义的复制构造函数。) - Daniel Langr
@DanielLangr,是的,我刚读了这个答案。基于评论 - 在C++14及以上版本中,std::string不是平凡可复制的 - Joseph D.
1
@codekaizer 不是的,请看下一个评论:“包含std::string的对象从未被视为可以平凡复制”。 - Daniel Langr

2

这样做会产生问题。所有引用和指针都将被复制,包括指向raw_data的指针,它将与源对象相同。

为了使用memcpy,您的类应该满足以下要求:

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

如果指针没有拥有它们所指向的数据,那么它们就没问题。否则,我们会出现同一内存的多次删除。 - Aconcagua
就像 @Aconcagua 所说的一样。已编辑。 - Attersson
当然,它们也可以被引用计数。至于写时复制机制,这就是为什么我会小心谨慎地对待这样一个强有力的陈述。 - Daniel Langr
1
我又编辑了一次,但这应该就是了。关于引用和指针的注意事项是必须的。 - Attersson
如果您有引用成员,并且执行memcpy操作,则意味着您正在重新绑定引用。这是非法的。 - curiousguy
你觉得我应该加逗号吗?“没有引用,(除非有指针……)”? - Attersson

1
正如其他人所说,为了使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(),这是一种编译器可能或可能不会应用的优化。

0

从这个有限的片段中,很明显raw_data是一个成员,指向一个new[]分配的T数组的指针。如果你复制对象,你会复制这个指针。你不会复制数组。

看一下你的析构函数。它可能无条件地调用delete[]。它不知道有多少个副本存在。这意味着它调用delete[]太频繁了。这是可以解决的:你需要类似于shared_ptr的东西。这并不是非常简单;你必须担心共享计数的线程安全性。显然,你不能只是memcpy对象,因为那样不会更新共享计数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接