如何确定类型是否为 memcpy-save?

How do I determine if a type is memcpy-save?

我和喜欢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 class,memcpy 被错误地使用了。我需要使用哪种类型特征才能 select 正确操作并消除警告?

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

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

还可以考虑使用 <algorithm> 中的 std::copy_n 来简化后备逻辑。

在 godbolt.org 上试用:Demo

对于C++11,你可以使用std::enable_if到select在编译时使用哪个实现类似于C++17if 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 语义正确的地方,允许编译器执行自己的优化。对比一下使用-O3fancy_copy and std::copy_n之间的二进制,自己看看。它们完全相同。