550

我对reinterpret_castvs的适用性有点困惑static_cast。从我读过的内容来看,一般规则是在编译时可以解释类型时使用静态强制转换,因此使用static. 这也是 C++ 编译器在内部用于隐式转换的转换。

reinterpret_casts适用于两种场景:

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

我有点困惑的是我需要的一种用法,我从 C 调用 C++,并且 C 代码需要保留 C++ 对象,所以基本上它包含一个void*. 应该使用什么类型转换来在 thevoid *和 Class 类型之间进行转换?

我见过两者的用法static_castreinterpret_cast?虽然从我一直在阅读的内容来看,它似乎static更好,因为演员可以在编译时发生?虽然它说用于reinterpret_cast从一种指针类型转换为另一种?

4

11 回答 11

519

C++ 标准保证以下内容:

static_cast指向和返回的指针会void*保留地址。也就是说,在下面,abc指向同一个地址:

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

reinterpret_cast仅保证如果您将指针转换为不同的类型,然后将reinterpret_cast其返回到原始类型,您将获得原始值。所以在下面:

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

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

对于从 的转换void*static_cast应该是首选。

于 2009-02-21T16:42:12.010 回答
184

一种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; }
于 2009-02-21T19:06:14.473 回答
155

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

完整答案:

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

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

另一方面,当您调用reinterpret_castCPU 时不会调用任何计算。它只是将内存中的一组位视为具有另一种类型。因此,当您使用此关键字转换int*为时float*,新值(在指针取消引用之后)与数学意义上的旧值无关。

示例:由于一个原因,它确实reinterpret_cast不可移植 - 字节顺序(字节序)。但这通常是使用它的最佳理由。让我们想象一下这个例子:你必须从文件中读取二进制 32 位数字,并且你知道它是大端。您的代码必须是通用的,并且可以在大端(例如某些 ARM)和小端(例如 x86)系统上正常工作。所以你必须检查字节顺序。它在编译时是众所周知的,因此您可以编写constexpr函数:您可以编写一个函数来实现这一点:

/*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(小端)。在重新解释转换后,p指针下的字节可以分别是0000'00000000'00010000'0001如果您使用静态转换,无论使用什么字节序,它都将始终为

编辑:

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

于 2017-04-07T08:58:07.223 回答
21

reinterpret_castC++ 标准没有定义的含义。因此,理论上 areinterpret_cast可能会使您的程序崩溃。在实践中,编译器会尝试做你期望的事情,即解释你传入的内容,就好像它们是你要转换的类型一样。如果您知道要使用的编译器做什么,reinterpret_cast 您可以使用它,但说它是可移植的将是在撒谎。

对于您描述的情况,以及您可能考虑的几乎任何情况reinterpret_cast,您可以使用static_cast或其他替代方法。除其他事项外,该标准还说明了您可以期待的内容static_cast(第 5.2.9 节):

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

因此,对于您的用例,标准化委员会打算让您使用static_cast.

于 2009-02-21T16:38:15.530 回答
10

reinterpret_cast 的一种用途是,如果您想对 (IEEE 754) 浮点数应用按位运算。一个例子是快速平方根逆技巧:

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

它将浮点数的二进制表示视为整数,将其右移并从常数中减去,从而将指数减半并取反。转换回浮点数后,对其进行 Newton-Raphson 迭代以使该近似值更精确:

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。

于 2016-01-12T16:54:06.553 回答
4

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

#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地给出了数据字段的值。生成的指针reinterpret_castb的内存位置视为普通 A 对象,因此当指针尝试获取数据字段时,它会返回一些特定于 B 的数据,就好像它是该字段的内容一样。

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

int i; unsigned int u = reinterpret_cast<unsigned int>(&i);

于 2019-10-23T20:06:26.623 回答
3

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

于 2014-10-29T17:12:47.023 回答
1
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

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

于 2013-01-18T14:38:38.570 回答
1

首先,您在此处有一些特定类型的数据,例如 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

或者

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

//this could be used in c and cpp

简而言之:这意味着相同的内存被用作不同的类型。因此,您可以将浮点数的二进制表示形式转换为像上面那样的 int 类型的浮点数。例如,0x80000000 为 -0(尾数和指数为空,但符号 msb 为一。这也适用于双精度数和长双精度数。

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

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

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
于 2017-02-23T12:10:27.070 回答
-8

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

于 2016-11-10T08:49:01.823 回答
-19

阅读常见问题解答!在 C 中保存 C++ 数据可能是有风险的。

在 C++ 中,指向对象的指针可以在void *不进行任何强制转换的情况下转换为。但反过来就不是这样了。您需要 astatic_cast来取回原始指针。

于 2009-02-21T16:31:02.333 回答