我应该使用it还是static_cast<void*>然后再用static_cast<myType*>来避免使用reinterpret_cast?

6

我看到有人建议使用static_cast<SomeType*>(static_cast<void*>(p))而不是reinterpret_cast进行强制类型转换。

我不明白这样做为什么更好,有人可以解释一下吗?

为了举例说明reinterpret_cast的必要性,以下是一个场景:

DWORD lpNumberOfBytes;
ULONG_PTR lpCompletionKey;
LPOVERLAPPED lpOverlapped;
GetQueuedCompletionStatus(myHandle, &lpNumberOfBytes, &lpCompletionKey, &lpOverlapped, 0);
if(lpCompletionKey == myCustomHandlerKey){
    auto myObject = reinterpret_cast<MyObject*>(lpOverlapped);  //i know this is really a MyObject
}

这是我听到的建议:
auto myObject = static_cast<MyObject*>(static_cast<void*>(lpOverlapped));

编辑:我最初在评论部分中提到,在此处使用static_cast而不是reinterpret_cast,http://blogs.msdn.com/b/vcblog/archive/2014/02/04/challenge-vulnerable-code.aspx,但是回想起来,我的问题来源并不重要。


reinterpret_cast 几乎总是由实现定义,发生的情况并不保证可移植。 - PlasmaHH
1
@PlasmaHH 这有点误导人。reinterpret_cast 有非常明确定义的语义,只是非常有限。但在这种情况下,语义似乎已经被覆盖了(我没有查看代码,基于 OP 对 lpOverlapped 的陈述)。但阅读 asdf 的评论则是完全不同的情况。 - Konrad Rudolph
4
在C++11中,当你从一个指针类型转换为另一个指针类型时,reinterpret_cast 的规定与 static_cast<cv ToType*>(static_cast<cv void*>(p)) 相同。这两者是相同的。 - Simple
1
另外,如果您想确保转换是安全的,可以使用类似这样的东西,然后使用pointer_cast<MyObject>(lpOverlapped) - Simple
2个回答

7

§5.2.10描述了reinterpret_cast可以执行的法律映射,并指定“不能执行其他转换”。

与您的示例相关的转换是/7:

对象指针可以显式转换为不同对象类型的指针。当类型为“指向T1的指针”的prvalue v转换为类型“指向cvT2的指针”时,如果T1T2均为标准布局类型...且T2的对齐要求不比T1更严格,则结果为static_cast<cv T2*>(static_cast<cv void*>(v)) [强调是我的]

任何其他指向对象类型的指针的转换结果都是“未指定的”。1

这是reinterpret_cast危险的两个原因之一:其转换仅对对象指针类型的子集定义良好,并且编译器通常不提供关于意外误用的诊断。

第二个原因是编译器甚至不会检查您尝试执行的映射是否合法,以及将执行哪个(在语义上完全不同的)映射。
最好明确告诉编译器(和读者)您想要执行的预期转换是哪一个。话虽如此,asdf的评论并不完全正确,因为您可能想要通过reinterpret_cast执行的并非所有转换都等同于使用static_cast<void*>后跟对目标类型的static_cast。

1 顺便提一下:简而言之(并稍加简化),“标准布局类型” 是一种没有虚函数或混合成员可见性的类型(或类型数组),它的所有成员和基类也都是标准布局。一个类型的对齐方式是其在内存中可能被定位的地址的限制。例如,许多机器要求 double 的地址必须是8的倍数。


+1 分钟引用标准并解释最重要的部分...如果您能列出和解释所有个别要求,我会给予+2 分钟。就像我在我的回答中忘记了对齐一样。 - Dima Tisnek
在什么情况下,双重static_cast被定义为重新解释定义和身份双重转换之外的情况? - Yakk - Adam Nevraumont
@Yakk,我不太确定你的意思,但如果我理解正确,双重static_cast via void*reinterpret_cast`更加严格而不是更加通用。这就是偏爱它的原因:它的语义更清晰。 - Konrad Rudolph
@KonradRudolph reinterpret_cast表示:“如果 condition 成立,则等同于 static_cast<T*>(static_cast<void*>(X))”。 现在,如果将双重 static_cast 显式写出来,只要 任何一个 条件成立,或者 XT*(即使条件不成立!),这种写法是有效的。那么还有其他情况下,这种双重 static cast 会产生定义行为吗? - Yakk - Adam Nevraumont
@Yakk 说实话,我不知道。在阅读我回答中引用的部分之前,我实际上会说这个转换是非法的,因为我认为在将其转换为void*之后,唯一合法的转换是回到原始类型。对于非指针T,我仍然认为这是正确的,但现在我不确定了。 - Konrad Rudolph

2
asdf 在链接的帖子中简洁明了地解释了这个问题。
因为编译器在程序的这一点上并不知道 CustomImage 是从 Image 派生出来的。
就我个人而言,我不想从 msdn 下载垃圾来深入研究和回答这个问题。毕竟这是一个编码挑战,你应该自己去解决它。
我的 C++ 类型转换规则如下:
1.使用 C++ 风格的转换 xx_cast<T*>,而不是 C 风格的 (T*),因为“显式比隐式更好”。
2.只有在确实需要时才使用 reinterpret_cast。
3.如果使用 reinterpret_cast<T*>,请确保转换/还原操作完全对称,例如:
T* obj = ...;
void* tmp = reinterpret_cast<void*> obj;
T* ref = reinterpret_cast<T*> tmp;  // T* obj --> T* ref

在这里,你必须确保objref是完全相同的类型,包括const限定符、类继承、对齐方式、内存类型(嵌入式)等任何你能想到的东西。


你能详细说明一下“应用/重新应用必须是完全镜像”的意思吗?你的意思是规范确保我们应该有obj == ref吗?还是你的意思是我们自己应该小心地只对那些obj == ref的类型使用reinterpret_cast,而任何其他用法可能会导致未定义或实现定义的行为? - nodakai

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