看起来C++有两种类型。实用的C++和专业的C++。在某些情况下,将一种类型的位模式解释为另一种类型可能是有用的。浮点数技巧是一个显著的例子。让我们以著名的快速反平方根为例(取自Wikipedia,该页面又引用了here):
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}
撇开细节不谈,它使用IEEE-754浮点位表示的某些属性。这里有趣的部分是从
float*
到long*
的*(long*)
转换。在C和C++之间有关于此类重新解释转换的哪种类型被定义为行为的差异,但是在实践中,这种技术经常在两种语言中使用。问题在于对于这样一个简单的问题,上述方法和其他不同的方法可能会出现很多陷阱。举几个例子: 同时,有很多方法可以执行类型强制转换和与之相关的机制。以下是我能找到的所有内容:
reinterpret_cast
and c-style cast[[nodiscard]] float int_to_float1(int x) noexcept { return *reinterpret_cast<float*>(&x); } [[nodiscard]] float int_to_float2(int x) noexcept { return *(float*)(&x); }
static_cast
andvoid*
[[nodiscard]] float int_to_float3(int x) noexcept { return *static_cast<float*>(static_cast<void*>(&x)); }
std::bit_cast
[[nodiscard]] constexpr float int_to_float4(int x) noexcept { return std::bit_cast<float>(x); }
memcpy
[[nodiscard]] float int_to_float5(int x) noexcept { float destination; memcpy(&destination, &x, sizeof(x)); return destination; }
union
[[nodiscard]] float int_to_float6(int x) noexcept { union { int as_int; float as_float; } destination{x}; return destination.as_float; }
placement
new
andstd::launder
[[nodiscard]] float int_to_float7(int x) noexcept { new(&x) float; return *std::launder(reinterpret_cast<float*>(&x)); }
std::byte
[[nodiscard]] float int_to_float8(int x) noexcept { return *reinterpret_cast<float*>(reinterpret_cast<std::byte*>(&x)); }
std::launder
或C++20中的std::byte
,std::bit_cast
?举一个具体的问题:重新编写快速反平方根函数最安全、最高效和最好的方法是什么?(是的,我知道维基百科上有一种方法的建议)。
编辑:为了增加混乱,似乎有一个提案建议添加另一种类型切换机制:
std::start_lifetime_as
,这也在另一个问题中讨论过。(godbolt)
std::bit_cast
和memcpy
不会导致未定义行为(UB)。 - Jarod42std::bit_cast
仅适用于 C++20 及更高版本...但肯定是现代的方式。 - Serge Ballesta