const 正确清洗 pods(普通旧数据)

const correct laundering of pods (plain old data)

为了绕过别名和字节重新解释规则,我有一个名为 T* landry_pod<T>(void*) 的实用函数,它假装复制字节并创建新对象。它在优化下编译为零,因为每个编译器都可以看到我把字节放回了它们开始的地方。

template<class T>
T* laundry_pod( void* data ){
  static_assert( std::is_pod<T>{} ); // or more verbose replacement as pod is gone
  char tmp[sizeof(T)];
  std::memcpy( tmp, data, sizeof(T) );
  T* r = ::new(data) T;
  std::memcpy( data, tmp, sizeof(T) );
  return r;
}

这确保 sizeof(T) 位数据点是相同的,但是 returns 指向 T 类型对象的指针。当 data 仅指向位但 而不是 实际对象时,这是一种符合标准的方式来执行 T* r = (T*)data; 。它在运行时优化到 0 条指令。

遗憾的是,虽然它在运行时什么都不做,但逻辑上不能在 const 缓冲区上使用。

这是我尝试修改它以使用 const 输入和输出:

template<class T, std::enable_if_t<std::is_const<T>{}, bool> = true, class In>
T* laundry_pod( const In* data ){
  static_assert( sizeof(In)==1 ); // really, In should be byte or char or similar
  static_assert( std::is_pod<T>{} ); // or more verbose replacement as pod is gone
  std::byte tmp[sizeof(T)];
  std::memcpy( tmp, data, sizeof(T) ); // copy bytes out
  for(std::size_t i =0; i<sizeof(T); ++i)
    data[i].~In(); // destroy const objects there // is this redundant?
  auto* r = ::new( (void*)data ) std::remove_const_t<T>; // cast away const on data (!)
  std::memcpy( r, tmp, sizeof(T) ); // copy same bytes back
  return r;
}

在这里,我销毁了常量对象(以及字节),然后在它们的位置构造了一个新对象。

上面应该优化为 0 条指令(试试看),但我在创建 r 时不得不放弃 const。

如果 data 指向连续的 const char 或 byte 缓冲区,销毁这些对象是否足以让我重用存储并保留在定义的行为范围内?还是仅仅创建新对象就足够了?还是我命中注定?

假设没有人使用旧的 In 指针事先访问原始字节。

您问题的核心是重用 const 对象的存储定义行为吗?答案是否定的,根据 basic.life#9:

Creating a new object at the storage location that a const object with static, thread, or automatic storage duration occupies or, at the storage location that such a const object used to occupy before its lifetime ended results in undefined behavior.

[ Example:

struct B {
  B();
  ~B();
};

const B b;

void h() {
  b.~B();
  new (const_cast<B*>(&b)) const B;             // undefined behavior
}

end example ]

表面上这是因为数据可能放在read-only内存中。因此,尝试修改 const 数据是 no-op.

也就不足为奇了

要添加到其他答案中,原始函数实际上根本不是身份 - 您仍然导致读取 和写入 原始缓冲区,这可能在并发使用场景中与普通读取具有不同的语义。特别是,多个线程在同一个缓冲区上同时调用 laundry_pod 是 UB。