【问题标题】:How do I determine if a type is memcpy-save?如何确定类型是否为 memcpy-save?
【发布时间】:2021-02-25 16:16:18
【问题描述】:

我与非常喜欢memcpy(太)的同事一起工作。我正在移植通常处理简单类型的代码(主要是doubles),可以使用memcpy 安全地复制它,并将其扩展到非平凡类型。我想编写一个简单的复制函数,它根据所讨论的类型做正确的事情:

#include <string.h>
#include <vector>
#include <type_traits>

template<class T>
void fancy_copy(const T* src,
                T* dest,
                unsigned int size)
{
  // here:
  if(std::is_trivially_copy_assignable<T>::value)
  {
    memcpy(dest, src, sizeof(T) * size);
  }
  else
  {
    for(unsigned int i = 0; i < size; ++i)
    {
      dest[i] = src[i];
    }
  }
}

class Custom
{
private:
  int value;
public:
  Custom& operator=(const Custom& other)
  {
    value = other.value + 1;
    return *this;
  }
};

int main()
{
  const unsigned int size = 10;

  {
    std::vector<int> source(size, 0);
    std::vector<int> target(size, 0);

    fancy_copy<int>(source.data(), target.data(), size);
  }

  {
    std::vector<Custom> source(size);
    std::vector<Custom> target(size);

    fancy_copy<Custom>(source.data(), target.data(), size);
  }

  return 0;
}

我使用 C++ 中内置的type_traits 来确定要使用的实现。不幸的是,当我使用g++ (10.2) 使用-Wall 编译代码时,我收到了警告

warning: ‘void* memcpy(void*, const void*, size_t)’ writing to an object of type ‘class Custom’ with no trivial copy-assignment; use copy-assignment or copy-initialization instead [-Wclass-memaccess]

所以,对于我的Custom 班级,memcpy 的使用不正确。我需要使用哪种类型特征来选择正确的操作并使警告静音?

【问题讨论】:

  • 我认为您有此错误,因为即使 is_trivially_copy_assignable 为假,编译器仍会看到 memcpy 代码。您是否尝试过改用std::enable_if
  • is_trivially_copyable 告诉你它是否可以被内存复制
  • “一个做正确事情的简单复制函数” - 我称之为std::copy。标准库实现者通常会非常非常好地优化此类常用算法。
  • @freakish:不。正如 StoryTeller 正确指出的那样,std::copy 确实是默认副本。它适用于所有可复制类型,并且经常进行优化。它甚至可以击败memcpy,因为memcpy 也必须支持未对齐的副本。但是doubledouble 是对齐的。

标签: c++ memcpy typetraits


【解决方案1】:

为此使用的正确类型特征是std::is_trivially_copyable,而不是std::is_trivially_copy_assignable

要修复警告,请使用if constexpr 而不是if 以便在编译时执行检查,并且只生成两个分支之一作为给定类型T 的无条件逻辑。即使由于运行时条件逻辑导致格式错误的调用无法访问,编译器也会发出警告,因为该调用仍然存在于生成的代码中。

还可以考虑使用&lt;algorithm&gt; 中的std::copy_n 来简化您的回退逻辑。

在 godbolt.org 上试用:Demo

对于 C++11,您可以使用 std::enable_if 选择在编译时使用哪个实现,类似于 C++17 if constexpr

template<class T>
typename std::enable_if<std::is_trivially_copyable<T>::value>::type
fancy_copy(const T* src, T* dest, unsigned int size)
{
  memcpy(dest, src, sizeof(T) * size);
}

template<class T>
typename std::enable_if<!std::is_trivially_copyable<T>::value>::type
fancy_copy(const T* src, T* dest, unsigned int size)
{
  std::copy_n(src, size, dest);
}

在 godbolt.org 上试用:Demo

最终,正如其他人所指出的那样,这个fancy_copy 可能是一个过早的优化,你最好在语义正确的地方使用std::copy_n,允许编译器执行自己的优化。在使用-O3 时,比较fancy_copystd::copy_n 之间的二进制文件,自己看看。它们完全相同。

【讨论】:

  • 我什至不知道constexpr if 。有什么方法可以在 C++17 之前实现同样的效果吗?
  • @hfhc2 你使用的是 C++11 还是 14?
  • 好吧,我怕是要辞职C++11了
  • 是的。 std::copy_n 非常快,当编译器可以计算对齐时,有时甚至比 memcpy
  • 这应该是要点。仍在 C++ 代码中使用 memcpy 只是货物崇拜编程。
猜你喜欢
  • 2011-06-11
  • 2017-06-23
  • 2010-12-11
  • 2010-11-13
  • 2011-08-21
  • 2017-01-06
  • 2014-11-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多