为什么C++中有reinterpret_cast,当两个链接的static_cast可以完成它的工作?

44

假设我想将A*转换为char*,反之亦然,我们有两种选择(我的意思是,很多人认为我们有两种选择,因为两者似乎都可以工作!这就是混淆所在!):

struct A
{
    int age;
    char name[128];
};

A a;
char *buffer = static_cast<char*>(static_cast<void*>(&a)); //choice 1
char *buffer = reinterpret_cast<char*>(&a); //choice 2

两种方式都可以正常工作。

//convert back
A *pA = static_cast<A*>(static_cast<void*>(buffer)); //choice 1
A *pA = reinterpret_cast<A*>(buffer); //choice 2

即使这样也可以正常工作!

那么,既然在C++中两个链接的static_cast就能完成reinterpret_cast的工作,为什么还需要reinterpret_cast呢?

你们中的一些人可能会认为这个话题是先前主题的副本,例如此帖子底部列出的那些主题,但事实并非如此。那些主题只讨论理论问题,但它们中没有一个例子能够说明为什么真正需要reinterpret_cast,以及为什么static_cast 至少需要两次才能达到目标。 我同意,一次static_cast是不够的。 但是两次呢?

如果两个链接的static_cast语法看起来很麻烦,那么我们可以编写一个函数模板来使其更加友好:

template<class To, class From>
To any_cast(From v)
{
    return static_cast<To>(static_cast<void*>(v));
}

然后,我们可以这样使用:

char *buffer = any_cast<char*>(&a); //choice 1
char *buffer = reinterpret_cast<char*>(&a); //choice 2

//convert back
A *pA = any_cast<A*>(buffer); //choice 1
A *pA = reinterpret_cast<A*>(buffer); //choice 2
此外,请参见这种情况,any_cast 可以很有用:Proper casting for fstream read and write member functions
那么我的问题基本上是,
  • 为什么C++中有 reinterpret_cast
  • 请给我展示一个例子,即使是两个链接在一起的 static_cast ,也肯定无法完成相同的工作?


1
嗯...我需要一个类型为T*且地址为8的指针(用于这个技巧http://stackoverflow.com/questions/5014061/whats-the-use-of-atl-packing-constant-when-computing-distance-from-the-start-of) - 我该如何使用static_cast实现? - sharptooth
@sharptooth:我并没有真正理解那个。请发一个回答,解释和演示两个链接的static_cast无法完成reinterpret_cast的工作。 - Nawaz
2
@Nawaz - 我不喜欢你的“cast-style”模板函数any_cast,因为它的名称具有误导性。如果你从类型pointer-to-A转换到pointer-to-B,它会做得很好,但并非所有情况都适用(“任何”情况)。 - 0xbadf00d
7个回答

36

reinterpret_cast 能做到的事情,用一系列的 static_cast 无法做到(都来自于 C++03 5.2.10):

  • 一个指针可以被明确转换为足够大的任何整数类型。

  • 一个整数型或枚举型的值可以被明确转换为一个指针。

  • 一个函数指针可以被明确转换为不同类型的函数指针。

  • 如果 T1T2 是函数类型或对象类型,那么类型为“X 的成员指针的 rvalue,其类型为 T1”,可以被明确转换为类型为“Y 的成员指针的 rvalue,其类型为 T2 ”。

此外,根据 C++03 9.2/17:

  • 一个指向 POD 结构体对象的指针,使用 reinterpret_cast 适当转换后,指向其初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然。

3
@Nawaz:“int i = 42; char* p;” 现在,“p = static_cast<X>(static_cast<Y>(i));” 对于所有可能的“X”和“Y”值都将无法通过编译(除非它们是提供转换函数的类类型)。 - j_random_hacker
6
第五条,POD结构体的第一个元素的指针可以转换为指向该结构体的指针,反之亦然。 - Cheers and hth. - Alf
1
@Nawaz:我认为前两个很简单;无论如何,@sharptooth的回答给出了第二种情况的示例,第一种情况只是其反向操作。我想不出后两种情况的引人注目的示例:虽然可以为它们构造示例,但我不知道真正有用的示例。这些项目只是“reinterpret_cast可以做到但没有任何static_cast序列可以做到的事情列表”。 - James McNellis
1
@James:据我所知,在C++98/03中不行。但标准明确保证了reinterpret_cast的转换。 - Cheers and hth. - Alf
1
@Nawaz:我记得它在类部分介绍的末尾。请查看我的旧博客文章http://alfps.wordpress.com/2010/06/14/c-basics-five-lesser-known-special-cast-operations/#first_member。 - Cheers and hth. - Alf
显示剩余9条评论

16

您需要使用reinterpret_cast来获得一个带有硬编码地址的指针(例如这里):

int* pointer = reinterpret_cast<int*>( 0x1234 );

你可能希望有这样的代码来访问某个内存映射设备的输入输出端口。


这个不能用链式的 static_cast 实现吗?有人试过吗? - Nawaz
@nawaz:我不知道如何使用static_cast来做到这一点,而且我从未见过有人这样做 - 要么使用C风格的转换,要么使用reinterpret_cast - sharptooth
@sharptooth:我的意思是,使用any_cast(如我问题中所定义的)?会失败吗? - Nawaz
@sharptooth:看起来你没有理解问题的要点。 - Nawaz
5
@Nawaz:这完美回答了你的问题。你的 any_cast 在这种情况下不起作用。你不能将 int 静态转换为 void:"error: invalid static_cast from type 'int' to type 'void'"。 - Brent Bradburn
显示剩余2条评论

6
一个具体的例子:
char a[4] = "Hi\n";
char* p = &a;

f(reinterpret_cast<char (&)[4]>(p));  // call f after restoring full type
      // ^-- any_cast<> can't do this...

// e.g. given...
template <typename T, int N>   // <=--- can match this function
void f(T (&)[N]) { std::cout << "array size " << N << '\n'; }

6
除了其他人提供的实际原因,存在它们所能做的不同之处,这是一件好事,因为它正在执行不同的工作。
static_cast 的意思是请将类型为 X 的数据转换为 Y。 reinterpret_cast 的意思是请将类型为 X 的数据解释为 Y。
在许多情况下,底层操作可能是相同的,并且任何一个都可以工作。但是,在说“请将 X 转换为 Y”和说“是的,我知道此数据声明为 X,但请使用它就像它真的是 Y 一样”之间存在概念上的差异。

1
这句话“static_cast 要求将类型为 X 的数据转换为 Y。reinterpret_cast 要求将 X 中的数据解释为 Y。”并没有帮助理解,因为它引发了另一个问题:在这种情况下,“转换”和“解释”的区别是什么? - Nawaz
转换意味着编程转换,例如将浮点值转换为整数。而“解释”意味着“我知道我说这是一个指向整数的指针,但实际上它不是,它是一个指向浮点数的指针,请按照这样对待”。 - jcoder
那又怎样呢?就理解它们的区别而言,这有何影响?我们现在学习的不是英语。当使用static_cast进行类型转换时,一个人可以说“我知道这是int,但是将其解释为float”。这有帮助吗? - Nawaz
2
+1 - 这在概念上非常关键,尽管有一些解决方法,例如给定 int x,您可以使用 reinterpret_cast<float&>(x),但不能使用 static_cast<float&>(x),但可以链式调用 *static_cast<float*>(static_cast<void*>(&x))(请注意,any_cast 不是一个干净的替代品:您需要额外的解引用)。至关重要的是,static_cast<float>(x) 执行非常不同的操作:将值从 int 转换时进行四舍五入。 - Tony Delroy

3
据我了解,您的选择1(两个链接的static_cast)是可怕的未定义行为。 Static_cast仅保证将指针转换为void *,然后再转换回原始指针的方式可以正常工作,并且这两次转换的结果指向原始对象。所有其他转换都是未定义的。对于对象指针(用户定义类的实例),static_cast可能会改变指针值。
对于reinterpret_cast-它只会改变指针的类型,据我所知-它从不触碰指针值。
因此,从技术上讲,这两个选择是不等价的。
编辑:参考文献,static_cast在当前C++0x草案的第5.2.9节中进行描述(很抱歉,没有C ++03标准,我认为当前的草案是n3225.pdf)。它描述了所有允许的转换,我想任何未经特别列出的东西都是UB。因此,如果选择这样做,它可能会破坏您的计算机。

未定义行为在什么情况下发生?这取决于数据类型! - Nawaz
4
标准保证,类型* p 和类型* q = static_cast<type *>(static_cast<void *>(p)) 指向同一对象。现在做 static_cast<type2 *>(static_cast<void *>(p)),其中 type2 与 type 不同就是未定义行为(虽然它通常可以工作)。如果您尝试使用继承的静态转换链(特别是多重继承),您将会看到差异。 - Tomek
1
这是一个很好的观点!如果一个类使用多重继承,将静态转换为基类型指针不仅会改变类型 - 在某些情况下还会改变值。因此,在这个例子中,static_cast与reinterpret_cast在根本上是不同的。然而,@Nawaz的做法(首先转换为void*)可能仍然适用于他的“any_cast”。 - Brent Bradburn
1
static_cast和reinterpret_cast在将void*转换为另一种类型的指针时,都存在相同的未定义行为潜在问题。因此,"any_cast"可以完全替代reinterpret_cast来解决这个问题。 - Brent Bradburn
“它从不触及指针值。” 这是意图,但标准委员会忘记写下来。意图还包括2个静态= 1个重新解释。委员会也忘了。 - curiousguy

0

使用C风格的强制类型转换并不安全。它从不检查不同类型是否可以混合在一起。 C++强制类型转换可帮助确保按照相关对象进行类型转换(根据您使用的强制类型转换类型)。这比始终有害的传统C样式强制类型转换更推荐使用。


-1

听着,各位,你们真的不需要reinterpret_cast、static_cast,甚至其他两种C++样式的转换(dynamic*和const)。

使用C样式的转换既更短又能让你做到这四种C++样式的转换所能做到的一切。

anyType someVar = (anyOtherType)otherVar;

那么为什么要使用C++风格的转换?可读性。其次:因为更严格的转换可以提高代码安全性。

*好的,你可能需要动态转换


9
C风格的强制类型转换不能取代dynamic_cast - Nawaz
这不是关于dynamic_cast的问题。它与C、static和reinterpret有关。此外,dynamic依赖于RTTI。 - Alexander Rafferty
1
你已经写了:“你实际上不需要reinterpret_cast、static_cast,甚至是另外两种C++风格的转换(动态和常量)。” - Nawaz
除了不使用语言中推荐的成语、掩盖程序员的意图和可能导致噩梦般的调试过程之外,也不要宣扬-1。 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接