我对reinterpret_cast和static_cast的适用性有点困惑。根据我所读到的,一般规则是当类型可以在编译时解释时使用静态强制转换,因此是静态这个词。这也是c++编译器内部用于隐式类型转换的类型转换。
reinterpret_cast适用于以下两种情况:
将整数类型转换为指针类型,反之亦然
将一种指针类型转换为另一种。我得到的一般想法是,这是不可移植的,应该避免。
我有点困惑的地方是我需要的一种用法,我从C调用c++, C代码需要保持c++对象,所以基本上它持有一个void*。在void *和Class类型之间应该使用什么类型转换?
我已经看到使用static_cast和reinterpret_cast?虽然从我所读到的似乎静态更好,因为强制转换可以在编译时发生?虽然它说使用reinterpret_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。
下面是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);
简单的回答是:
如果你不知道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编译器拒绝编译它(这是正确的)。