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。
为了绕过别名和字节重新解释规则,我有一个名为 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。