为何std::bit_cast要求目标类型必须是平凡可构造的?
嘿,这个问题问到点子上了!咱们得先把std::bit_cast的本质摸透——它就是个“纯字节搬运工”,完全不管类型的常规构造逻辑,直接把一个对象的内存字节原封不动拷贝到另一个类型的对象里。那为啥非得卡死目标类型必须是平凡可构造的呢?咱们掰开了说:
首先是要绕开非平凡构造的“额外操作”。如果目标类型有非平凡构造函数,意味着创建它的时候得执行特定逻辑——比如给成员分配堆内存、初始化某个计数器、甚至调用第三方接口。但bit_cast的设计初衷就是跳过所有这些逻辑,纯纯复制字节。要是允许非平凡构造的类型,那等于直接无视了人家构造函数的约定,复制完的对象大概率处于“半残”状态,编译器根本没法保证这玩意儿能正常用,属于标准里的未定义行为。
其次是符合C对象生命周期的规则。C标准对对象的“出生”有严格要求:一个对象的生命周期必须从构造函数执行完成开始。如果目标类型是非平凡可构造的,你用bit_cast直接把字节怼进去,等于没调用构造函数就“造”出了对象,这直接违反了生命周期规则,妥妥的未定义行为。而平凡可构造的类型就不一样了,它的构造过程啥额外操作都没有(比如默认平凡构造就是啥也不做),直接复制字节就能合法让对象进入有效状态,完全符合标准要求。
再看实际实现的逻辑,比如常见的参考实现是这样的:
template<class To, class From> std::enable_if_t< sizeof(To) == sizeof(From) && std::is_trivially_copyable_v<From> && std::is_trivially_copyable_v<To>, To> bit_cast(const From& from) noexcept { static_assert(std::is_trivially_constructible_v<To>, "To must be trivially constructible"); To to; std::memcpy(&to, &from, sizeof(To)); return to; }你看,这里直接创建了一个To类型的对象
to,然后用memcpy把字节拷进去。如果To不是平凡可构造的,To to;这一步就会调用非平凡构造函数,可能已经对内存做了些操作,之后再用memcpy覆盖,等于先做了无用功,还可能留下隐患。而平凡可构造的类型,To to;要么啥也不做,要么只是简单的零初始化,memcpy覆盖后的状态就是我们要的原始字节状态,完全没毛病。
内容来源于stack exchange

