我对reinterpret_cast和static_cast的适用性有点困惑。根据我所读到的,一般规则是当类型可以在编译时解释时使用静态强制转换,因此是静态这个词。这也是c++编译器内部用于隐式类型转换的类型转换。

reinterpret_cast适用于以下两种情况:

将整数类型转换为指针类型,反之亦然 将一种指针类型转换为另一种。我得到的一般想法是,这是不可移植的,应该避免。

我有点困惑的地方是我需要的一种用法,我从C调用c++, C代码需要保持c++对象,所以基本上它持有一个void*。在void *和Class类型之间应该使用什么类型转换?

我已经看到使用static_cast和reinterpret_cast?虽然从我所读到的似乎静态更好,因为强制转换可以在编译时发生?虽然它说使用reinterpret_cast从一种指针类型转换为另一种?


当前回答

需要reinterpret_cast的一种情况是与不透明数据类型进行接口时。这种情况经常发生在程序员无法控制的供应商api中。这是一个人为的例子,供应商提供了一个API来存储和检索任意的全局数据:

// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();

要使用这个API,程序员必须将数据转换为VendorGlobalUserData,然后再转换回来。Static_cast不能工作,必须使用reinterpret_cast:

// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;

struct MyUserData {
    MyUserData() : m(42) {}
    int m;
};

int main() {
    MyUserData u;

        // store global data
    VendorGlobalUserData d1;
//  d1 = &u;                                          // compile error
//  d1 = static_cast<VendorGlobalUserData>(&u);       // compile error
    d1 = reinterpret_cast<VendorGlobalUserData>(&u);  // ok
    VendorSetUserData(d1);

        // do other stuff...

        // retrieve global data
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
//  p = d2;                                           // compile error
//  p = static_cast<MyUserData *>(d2);                // compile error
    p = reinterpret_cast<MyUserData *>(d2);           // ok

    if (p) { cout << p->m << endl; }
    return 0;
}

下面是示例API的一个人为实现:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }

其他回答

reinterpret_cast的一个用途是如果你想对(IEEE 754)浮点应用位操作。其中一个例子是快速平方根逆技巧:

https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code

它将浮点数的二进制表示形式视为整数,将其右移并从常数中减去,从而将指数对半和负。在转换回浮点数后,它会进行牛顿-拉弗森迭代,以使这个近似更加精确:

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 deuce? 
    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;
}

它最初是用C语言编写的,所以使用C类型强制转换,但类似的c++类型强制转换是reinterpret_cast。

阅读FAQ!在C语言中保存c++数据是有风险的。

在c++中,指向对象的指针不需要任何类型转换就可以转换为void *。但反过来就不是这样了。您需要一个static_cast来取回原始指针。

reinterpret_cast的含义不是由c++标准定义的。因此,理论上reinterpret_cast可能导致程序崩溃。在实践中,编译器试图做你所期望的事情,也就是解释你传入的二进制位,就好像它们是你要强制转换的类型一样。如果你知道你将要使用的编译器对reinterpret_cast做了什么,你就可以使用它,但是说它是可移植的是在撒谎。

对于您所描述的情况,以及您可能会考虑reinterpret_cast的大多数情况,您可以使用static_cast或其他替代方法。在其他事情中,标准有这样说你可以期待static_cast(§5.2.9):

类型为“指向cv void的指针”的右值可以显式转换为指向对象类型的指针。一个类型为pointer to object的值转换为" pointer to cv void "并返回到原始指针类型将有其原始值。

因此,对于您的用例,标准化委员会显然希望您使用static_cast。

简单的回答是: 如果你不知道reinterpret_cast代表什么,就不要使用它。如果你将来需要它,你会知道的。

完整的回答:

让我们考虑基本的数字类型。

例如,当你将int(12)转换为unsigned float (12.0f)时,你的处理器需要调用一些计算,因为这两个数字具有不同的位表示。这就是static_cast所代表的内容。

另一方面,当你调用reinterpret_cast时,CPU不会调用任何计算。它只是把内存中的一组位当作另一种类型来处理。因此,当你用这个关键字将int*转换为float*时,新值(指针解引用后)在数学意义上与旧值没有任何关系(忽略读取该值是未定义行为的事实)。

请注意,在reinterprt_cast'ing之后读取或修改值通常是未定义的行为。在大多数情况下,如果你想实现一些数据的位表示,你应该使用指向std::byte的指针或引用(从c++ 17开始),这几乎总是一个合法的操作。其他“安全”的类型是char和unsigned char,但我认为在现代c++中不应该将其用于此目的,因为std::byte具有更好的语义。

Example: It is true that reinterpret_cast is not portable because of one reason - byte order (endianness). But this is often surprisingly the best reason to use it. Let's imagine the example: you have to read binary 32bit number from file, and you know it is big endian. Your code has to be generic and works properly on big endian (e.g. some ARM) and little endian (e.g. x86) systems. So you have to check the byte order. It is well-known on compile time so you can write constexpr function: You can write a function to achieve this:

/*constexpr*/ bool is_little_endian() {
  std::uint16_t x=0x0001;
  auto p = reinterpret_cast<std::uint8_t*>(&x);
  return *p != 0;
}

解释:x在内存中的二进制表示形式可以是0000'0000'0000'0001(大)或0000'0001'0000'0000 '0000(小端)。重新解释后,p指针下的字节可以分别为0000'0000或0000'0001。如果使用静态强制转换,则无论使用什么字节顺序,它都将始终是0000'0001。

编辑:

在第一个版本中,我让示例函数is_little_endian为constexpr。它在最新的gcc(8.3.0)上编译良好,但标准说它是非法的。clang编译器拒绝编译它(这是正确的)。

首先你有一些特定类型的数据,比如这里的int:

int x = 0x7fffffff://==nan in binary representation

然后你想访问相同的变量作为其他类型,如float: 你可以在

float y = reinterpret_cast<float&>(x);

//this could only be used in cpp, looks like a function with template-parameters

or

float y = *(float*)&(x);

//this could be used in c and cpp

BRIEF:这意味着相同的内存被用作不同的类型。所以你可以像上面那样将浮点数的二进制表示形式转换为int类型。例如,0x80000000为-0(尾数和指数为空,但符号msb为1。这也适用于双打和长双打。

优化:我认为reinterpret_cast会在许多编译器中进行优化,而c-casting是由指针算术进行的(值必须复制到内存中,因为指针不能指向cpu-寄存器)。

注意:在这两种情况下,您都应该在强制转换之前将强制转换的值保存在变量中!这个宏可以帮助:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })