为memcpy创建类型安全的C++ wrapper?

5

鉴于 std::copy(显然仅适用于 Trivial Types)只能作为 memmove(*) 的包装器实现,我想知道:

  • 是否有标准的 C++ 类型安全的包装器可用于需要使用 memcpy 的情况?(我数不清有多少次忘记乘以 sizeof。)
  • 如果标准中没有这样的东西,是否有任何提案?如果没有,为什么没有?
  • 是否存在特定的障碍,使得提供一个自动执行 sizeof 乘法的 memcpy 包装器成为不可能?

(*): C++标准库实现(从MSVC 2005开始,一直到现代的MSVC2015、libc++等)将TriviallyCopyable类型的std::copy衰减为memmove。但不是memcpy。原因是:

  • std::copy(src_first, src_last, destination_first)的定义如下:

    如果 d_first 在范围 [first, last) 内,则行为未定义。

    • 只有目标范围的开头不允许在源范围内。目标范围可以延伸到源范围内。也就是说,d_first 可以在源范围的“左侧”,并且目标范围可以延伸到源范围内。
  • 对于 std::memcpy,定义如下:

    如果对象重叠,则行为未定义。

    • 也就是说,完整的范围不能重叠:这就是使 memcpy 成为最快变体的原因,因为它可以假定源和目标的内存完全不同。
  • 对于 std::memmove,定义如下:

    对象可能重叠:复制的过程就像将字符复制到临时字符数组中,然后从数组中将字符复制到 dest 中一样。

    • 也就是说,源范围和目标范围可以任意重叠,没有限制。
鉴于此,很明显对于TriviallyCopyable类型,可以使用std::memove来实现std::copy,因为memmove不会强制执行任何限制,并且通过类型特征可以在编译时分派到正确的实现。但是,使用memcpy来实现std::copy很难,因为(a)检查指针范围是否重叠必须在运行时进行,(b)即使对于无关的内存范围实现运行时检查可能会非常混乱。因此,这让我们只能
void* memcpy( void* dest, const void* src, std::size_t count );

一个界面不太好的函数,你需要不断地将非字符对象的输入计数与它们的sizeof相乘,并且它是完全无类型的。
但是memcpy是最快的(并且相差很大,可以自行测量),当你需要快速复制TriviallyCopyable类型时,你会使用memcpy。表面上应该很容易用类型安全的包装器包装,例如:
template<typename T>
T* trivial_copy(T* dest, T* src, std::size_t n) {
    return static_cast<T*>(std::memcpy(dest, src, sizeof(T) * n));
}

但是,现在还不清楚是否应该通过std::is_trival等方式进行编译时检查,当然也可能会有一些讨论,例如是否要使用确切的memcpy签名顺序等等。

那么,我真的需要自己重新发明这个轮子吗?它是否被讨论过用于标准库中?等等。



1
你为什么认为std::copy必须由memmove实现? - nwp
3
这种包装器有什么用途?为什么您不满意 std::copy 本身? - Mikhail
你能否举个例子,需要手动使用memcpy(或者手动复制)的情况?在几乎所有情况下,这些操作都是在后台使用复制构造函数和复制赋值运算符完成的。 - luc
@nwp 我认为 OP 的意思不是“必须使用memmove”,而是“不能使用memcpy,只能使用memmove”。 - user743382
1
“鉴于std::copy只能作为memmove的包装器实现” - 这是不正确的。std::copy的文档说明:“如果源范围和目标范围重叠,则行为未定义。”,这使它可以退化为memcpy - Mikhail
@Mikhail - 不正确。对于std::copy范围允许重叠,只要目标范围的开头不在源范围内即可。 - Martin Ba
1个回答

1
为了阐明mencpy和memove之间的区别,根据文档,memmove可以将内存复制到与源内存重叠的位置,而对于memcpy,这是未定义的行为。
"对象可能重叠:复制过程就像字符被复制到临时字符数组中,然后从数组复制到dest一样。"
引用块:
有没有标准的C ++类型安全包装器,用于需要memcpy的情况?(我数不清有多少次我忘记乘以sizeof。)
是的,std :: copy(也许在下面解释)。
引用块:
如果标准中没有任何内容,是否提出了此类建议?如果没有,则为什么?
据我所知,标准并不强制要求对于平凡类型,使用memmove / memcpy进行std :: copy。因此,它取决于实现。例如,在visual studio update 2015 update 2中,他们使用memmove来加速
"增加了std::vector重新分配和std::copy()的速度;它们通过调用memmove()来处理可平凡复制类型(包括用户定义的类型),速度提高了9倍。"

是否有提供自动执行sizeof乘法的memcpy包装器的特定障碍?

实际上,您可以使用std::is_trivial自己实现此功能。

编辑:

根据this document第25.3.1节,std::copy实现没有任何限制,仅涉及复杂性:

复杂度:恰好是last - first赋值。

考虑到memcpy使用cpu特定指令(并非所有cpu都可用)来加速内存复制,这是完全合理的。


@Martin Ba添加了一次编辑,请跟进,因为我今天将在Stack Code Review上发布另一种实现。 - Raxvan
就算是 MSVC 2005 版本,它也为 std::copy 做了 memmove 的优化(我认为在 2005 版本中他们使用了 memmove_s 在 2015 版本中则使用 memmove,因此 2015 版本确实进行了更多的优化。) - Martin Ba

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