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

reinterpret_cast适用于以下两种情况:

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

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

我已经看到使用static_cast和reinterpret_cast?虽然从我所读到的似乎静态更好,因为强制转换可以在编译时发生?虽然它说使用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。


c++标准保证了以下内容:

指向void*和来自void*的Static_casting指针将保留地址。也就是说,下面的a、b、c都指向同一个地址:

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

Reinterpret_cast只保证如果将一个指针强制转换为不同的类型,然后将其重新转换为原始类型,则得到原始值。下面是:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

A和c包含相同的值,但b的值未指定。(在实践中,它通常包含与a和c相同的地址,但标准中没有指定,而且在具有更复杂内存系统的机器上可能不是这样。)

对于void*和void*之间的强制转换,应该优先使用static_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; }

template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

我试图总结并使用模板编写了一个简单的安全强制转换。 注意,这个解决方案并不保证转换函数上的指针。


可以在编译时使用reinterprete_cast检查继承。 看这里: 在编译时使用reinterpret_cast检查继承


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。


快速回答:如果编译static_cast,则使用static_cast,否则使用reinterpret_cast。


首先你有一些特定类型的数据,比如这里的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__; })

简单的回答是: 如果你不知道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编译器拒绝编译它(这是正确的)。


下面是Avi Ginsburg程序的一个变体,它清楚地说明了Chris Luengo、flodin和cmdLP提到的reinterpret_cast属性:编译器将指向内存的位置视为新类型的对象:

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

class A
{
public:
    int i;
};

class B : public A
{
public:
    virtual void f() {}
};

int main()
{
    string s;
    B b;
    b.i = 0;
    A* as = static_cast<A*>(&b);
    A* ar = reinterpret_cast<A*>(&b);
    B* c = reinterpret_cast<B*>(ar);
    
    cout << "as->i = " << hex << setfill('0')  << as->i << "\n";
    cout << "ar->i = " << ar->i << "\n";
    cout << "b.i   = " << b.i << "\n";
    cout << "c->i  = " << c->i << "\n";
    cout << "\n";
    cout << "&(as->i) = " << &(as->i) << "\n";
    cout << "&(ar->i) = " << &(ar->i) << "\n";
    cout << "&(b.i) = " << &(b.i) << "\n";
    cout << "&(c->i) = " << &(c->i) << "\n";
    cout << "\n";
    cout << "&b = " << &b << "\n";
    cout << "as = " << as << "\n";
    cout << "ar = " << ar << "\n";
    cout << "c  = " << c  << "\n";
    
    cout << "Press ENTER to exit.\n";
    getline(cin,s);
}

结果是这样的输出:

as->i = 0
ar->i = 50ee64
b.i   = 0
c->i  = 0

&(as->i) = 00EFF978
&(ar->i) = 00EFF974
&(b.i)   = 00EFF978
&(c->i)  = 00EFF978

&b = 00EFF974
as = 00EFF978
ar = 00EFF974
c  = 00EFF974
Press ENTER to exit.

可以看到,B对象首先作为特定于B的数据构建在内存中,然后是嵌入的A对象。static_cast正确地返回嵌入的A对象的地址,并且由static_cast创建的指针正确地给出data字段的值。由reinterpret_cast生成的指针将b的内存位置视为普通的a对象,因此当指针试图获取数据字段时,它将返回一些特定于b的数据,就像它是该字段的内容一样。

reinterpret_cast的一个用途是将指针转换为无符号整数(当指针和无符号整数大小相同时):

int我; Unsigned int u = reinterpret_cast< Unsigned int>(&i);