当将void*转换为任何类型时,我应该使用static_cast还是reinterpret_cast?

263

在将void*指针转换为另一个指针类型时,static_castreinterpret_cast似乎都可以正常工作。是否有理由更喜欢其中一种?


82
显然你以前没有使用过 POSIX 线程。 - user470379
10
哇,这正是我在Stack Overflow上找到这个问题的原因!非常好的观察力 :-)。 - Ogre Psalm33
9个回答

210
使用static_cast:它是最狭窄的转换,正好描述了此处进行的转换。
有一种误解认为使用reinterpret_cast会更匹配,因为它意味着“完全忽略类型安全性,只是将A强制转换为B”。
然而,这实际上并没有描述reinterpret_cast的效果。相反,reinterpret_cast有多重含义,“由reinterpret_cast执行的映射是实现定义的。” [5.2.10.3]
但是,在从void*转换为T*的特定情况下,映射完全由标准定义;即将类型分配给无类型指针而不改变其地址。
这是偏爱static_cast的原因。
此外,更重要的是,每次使用reinterpret_cast都是相当危险的,因为它(对于指针)可以将任何内容转换为任何其他内容,而static_cast更加严格,因此提供了更好的保护级别。这已经让我避免了一些错误,其中我不小心试图将一个指针类型强制转换为另一个指针类型。

69

static_cast更适合将void*转换为其他类型的指针。

当两种类型之间存在一种自然、直观的转换,但不一定保证在运行时能够成功时,static_cast是首选的类型转换方式。例如,您可以使用static_cast将基类指针转换为派生类指针,在某些情况下这种转换是有意义的,但在运行时无法验证。类似地,您可以使用static_castint转换为char,这是明确定义的,但在执行时可能会导致精度损失。

另一方面,reinterpret_cast是一个设计用于执行根本不安全或不可移植的转换的强制转换操作符。例如,您可以使用reinterpret_castvoid *转换为int,如果您的系统恰好具有sizeof(void*)sizeof(int),则可以正确工作。您还可以使用reinterpret_castfloat *转换为int *或反之亦然,这是特定于平台的,因为floatint的特定表示不保证彼此具有任何共同之处。

简而言之,如果您发现自己正在进行一个转换,其中强制转换在逻辑上是有意义的,但运行时可能无法成功,请避免使用reinterpret_cast。如果您有一些先验知识,认为强制转换在运行时是有效的,并且向编译器传达“我知道这可能不起作用,但至少它有意义并且我有理由相信它将正确地执行正确的事情在运行时。”编译器可以检查强制转换是否在相关类型之间,如果不是,则报告编译时错误。使用reinterpret_cast来完成指针转换完全绕过了编译时的安全检查。

有一些情况下,您可能需要使用dynamic_cast而不是static_cast,但这些情况大多涉及类层次结构中的强制转换,有时直接涉及void*

至于规范更喜欢哪种,两者都没有过度提到“使用的正确方式”(或者至少我不记得有一个被提到过)。然而,我认为规范希望您使用static_cast而不是reinterpret_cast。例如,在使用C样式强制转换时,如:

A* ptr = (A*) myVoidPointer;

尝试类型转换操作的顺序总是先尝试使用static_cast,然后再尝试使用reinterpret_cast,这正是你想要的行为,因为reinterpret_cast不能保证可移植性。


2
澄清一下:作者在这里所说的“static_cast... isn't necessarily guaranteed to work at runtime”是指,“你的程序可能会在后面崩溃。” 如果您从基类型向派生类型进行static_cast,它将在运行时“工作”(即您不会收到异常或NULL指针),但如果涉及多重继承,则结果可能指向错误的内存位置。 (有关更多详细信息,请参见此答案。)只有dynamic_cast将使用RTTI进行运行时检查并在转换无效时优雅地失败。 - andrewtc

12

这是一个棘手的问题。一方面,Konrad 对于 reinterpret_cast 的规范定义提出了很好的观点,尽管在实践中它可能执行相同的操作。另一方面,如果你正在转换指针类型(例如通过char* 在内存中进行索引),static_cast 将会生成编译器错误,你将被迫使用reinterpret_cast

在实践中,我使用 reinterpret_cast,因为它更能描述强制类型转换的目的。当然,你可以为仅限于指针重新解释的不同运算符提供支持(以保证返回相同的地址),但标准中并没有这样的运算符。


8
“用不同的运算符来指定指针重新解释(保证返回相同地址)”是什么意思?那个运算符就是 reinterpret_cast - curiousguy
4
按照标准,@curiousguy所说的不正确。reinterpret_cast不能保证使用相同的地址,只有在你将一个类型重新解释为另一个类型然后再次转换回来时,你才能得到开始时相同的地址。 - ClydeTheGhost

3

你很可能是通过隐式转换获得了那个 void*,因此应该使用 static_cast,因为它最接近隐式转换。


2

使用static_castreinterpret_cast进行void*类型的转换是相同的。请参考此链接中的答案。但通常情况下,更倾向于使用static_cast,因为它更加精确且一般情况下(但不是在此特定情况下)更安全。


2
关于实现定义的映射存在混淆。这是关于“映射”的问题。实现可以内部映射任何它喜欢的方式,但必须提供其他保证。reinterpret_cast 的结果不能仅仅随意指向实现可能认为是其他对象位置的东西 - 尽管外部表示可能不同。 (尽管在特定情况下,转换为整数然后返回原始值是有可能的,详见以下说明)。基本上,无论实现的重新解释转换是否返回相同的“内存位置”,它返回的任何内容都被映射到相同的“值”上。 (顺便说一句,核心指南 明确回答了使用 reinterpret_cast(char*/unsigned char*/std::byte*)查看原始对象表示的定义行为的情况。)
相关标准规则 void* 转换:
static_cast:
一个类型为“指向cv1 void”的prvalue可以转换为一个类型为“指向cv2 T”的prvalue,其中T是对象类型,cv2是与cv1相同或更大的cv限定符。如果原始指针值表示内存中字节的地址A,并且A不满足T的对齐要求,则结果指针值是未指定的。否则,如果原始指针值指向对象a,并且存在一个类型为T(忽略cv限定符)的对象b,它与a可以进行指针互换(6.8.3),则结果是指向b的指针。否则,指针值通过转换不变。 [示例3: T* p1 = new T; const T* p2 = static_cast<const T*>(static_cast<void*>(p1)); bool b = p1 == p2; // b将具有true的值。 —end example]

reinterpret_cast

一个对象指针可以被显式转换为不同类型的对象指针。当具有对象指针类型的prvalue v被转换为对象指针类型“指向cv T的指针”时,结果是static_cast<cv T*>(static_cast<cv void*>(v))
关键在于最后一句话。对于此问题的void*转换,(并假设对象类型满足对齐要求、cv资格和安全派生指针),reinterpret_cast T* from void*等同于static_cast T* from void*
但你绝对应该使用static_cast,因为关于reinterpret_cast和ISO标准的复杂性的可怕传说可能会让你无端地受到同行的抨击。

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ru-pun


0
我建议始终使用最弱的转换方式。 reinterpret_cast 可以用来将指针转换为 float。转换的结构破坏程度越大,使用它的注意力就越多。
对于 char*,我会使用 C 风格的转换方式,直到我们有了一些 reinterpret_pointer_cast,因为它更弱且其他方式都不足够。

3
"reinterpret_cast 可能被用来将一个指针强制转换为浮点数。" 当然不是! - curiousguy
4
可能是 float f = *reinterpret_cast<const float*>(&p); - Ben Voigt
2
@BenVoigt 这是指针之间的转换;其中一个恰好是浮点指针。 - nodakai
8
@BenVoigt,“整个表达式”并不是一个强制类型转换。该表达式由应用于强制类型转换的取消引用组成。您声称可以将指针强制转换为 float,这是错误的。该表达式将 void ** 强制转换为 const float *,然后使用取消引用操作(这不是一个强制类型转换)将 const float * 转换为 float - M.M
2
@BenVoigt,您在回答某人提问“如何进行类型转换”时提供了该代码,当有人说该代码在指针之间进行类型转换时(它确实是这样),您说“不是”。 - M.M
显示剩余15条评论

0

使用 static_cast 进行类型转换。只有在极为罕见的情况下,当没有其他方法时才使用 reinterpret_cast


-3

reinterpret_cast 会强制将 void* 转换为目标数据类型。它不保证任何安全性,因为底层对象可能是任何东西,所以你的程序可能会崩溃。

例如,你可以将 myclass* 强制转换为 void*,然后使用 reinterpret_cast 将其转换为具有完全不同布局的 yourclass*

因此,最好和推荐使用 static_cast


3
static_cast不能防止这种情况发生。一旦一个指针退化成void*,你可以将它静态转换为任何类型的指针。 - Dan O

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