常量正确清洗 Pod(普通旧数据)

const correct laundering of pods (plain old data)

本文关键字:数据 清洗 Pod 常量      更新时间:2023-10-16

为了绕过混叠和字节重新解释规则,我有一个名为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)位数据点相同,但返回指向该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;
}

在这里,我销毁 const 对象(井字节),然后在它们的位置构造一个新对象。

以上应该优化为 0 条指令(试试吧),但是当我创建r时我不得不丢弃 const .

如果data指向常量字符或字节的连续缓冲区,那么销毁这些对象是否足以允许我重用存储并保持在定义的行为土地上? 或者简单地创建新对象就足够了? 还是我注定要失败?

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

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

在具有静态、线程或自动存储持续时间的 const 对象所占用的存储位置创建新对象,或者在此类 const 对象在其生存期结束之前曾经占用的存储位置创建新对象会导致未定义的行为。

[示例:

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

结束示例]

表面上,这是因为数据可能放在只读内存中。因此,尝试修改const数据是无操作也就不足为奇了。

为了补充其他答案,原始函数实际上根本不是标识 - 您仍然会导致原始缓冲区的读取和写入,这可能与并发使用方案中的普通读取具有不同的语义。特别是,在同一缓冲区上同时调用laundry_pod的多个线程是 UB。