通过void *而不是使用reinterpret_cast进行转换

问题描述 投票:37回答:3

我正在读一本书,我发现reinterpret_cast不应该直接使用,而是与static_cast结合使用* *

T1 * p1=...
void *pv=p1;
T2 * p2= static_cast<T2*>(pv);

代替:

T1 * p1=...
T2 * p2= reinterpret_cast<T2*>(p1);

但是,我找不到解释为什么这比直接演员更好。如果有人能给我一个解释或指出我的答案,我将非常感激。

提前致谢

附:我知道reinterpret_cast用于什么,但我从未见过以这种方式使用它

c++ casting void-pointers language-lawyer reinterpret-cast
3个回答
26
投票

对于允许这种演员表的类型(例如,如果T1是POD类型而T2unsigned char),static_cast的方法由标准明确定义。

另一方面,reinterpret_cast完全是实现定义的 - 你得到它的唯一保证是你可以将指针类型转换为任何其他指针类型然后返回,你将获得原始值;而且,你可以将指针类型强制转换为足以保存指针值的整数类型(根据实现而变化,根本不需要存在),然后将其强制转换,然后你将得到原始值。

更具体地说,我将引用标准的相关部分,突出重要部分:

5.2.10 [expr.reinterpret.cast]:

reinterpret_cast执行的映射是实现定义的。 [注意:它可能会,也可能不会产生与原始值不同的表示。] ...指向对象的指针可以显式转换为指向不同类型对象的指针。)除了转换类型的右值之外“指向T1的指针”到“指向T2的指针”(其中T1和T2是对象类型,T2的对齐要求不比T1更严格)并返回其原始类型产生原始指针值,结果这种指针转换是未指定的。

所以像这样:

struct pod_t { int x; };
pod_t pod;
char* p = reinterpret_cast<char*>(&pod);
memset(p, 0, sizeof pod);

实际上是未指定的。

解释为什么static_cast工作有点棘手。以上是使用static_cast重写的代码,我相信它始终按照标准的预期工作:

struct pod_t { int x; };
pod_t pod;
char* p = static_cast<char*>(static_cast<void*>(&pod));
memset(p, 0, sizeof pod);

再次,让我引用标准的各个部分,这些部分一起使我得出结论,上述内容应该是可移植的:

3.9 [basic.types]:

对于POD类型T的任何对象(基类子对象除外),无论对象是否保持类型T的有效值,组成对象的基础字节(1.7)都可以复制到char或unsigned数组中焦炭。如果将char或unsigned char数组的内容复制回对象,则该对象应随后保持其原始值。

类型T的对象的对象表示是由类型T的对象占据的N个无符号字符对象的序列,其中N等于sizeof(T)。

3.9.2 [basic.compound]:

cv-qualified(3.9.3)或cv-unqualified type void*(指向void的指针)的对象可用于指向未知类型的对象。一个void*应该能够容纳任何对象指针。符合cv资格或cv不合格(3.9.3)void*应具有与cv合格或cv不合格的char*相同的表示和对齐要求。

3.10 [basic.lval]:

如果程序试图通过不同于以下类型之一的左值访问对象的存储值,则行为未定义):

  • ...
  • char或unsigned char类型。

4.10 [conv.ptr]

类型为“指向cv T的指针”的右值,其中T是对象类型,可以转换为“指向cv void的指针”类型的右值。将“指向cv T的指针”转换为“指向cv的指针”的结果void“指向T类型的对象所在的存储位置的开头,就好像该对象是T类型的最派生对象(1.8)(即,不是基类子对象)。

5.2.9 [expr.static.cast]:

除了左值到右值(4.1),数组 - 拓扑(4.2),函数到指针(4.3)和布尔(4.12)转换之外,任何标准转换序列(第4节)的反转都可以执行显式使用static_cast。

[编辑]另一方面,我们有这个宝石:

9.2 [class.mem] / 17:

指向POD结构对象的指针,使用reinterpret_cast进行适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。 [注意:因此,在POD-struct对象中可能存在未命名的填充,但不是在其开头,以实现适当的对齐。 ]

这似乎意味着指针之间的reinterpret_cast某种意味着“同一地址”。去搞清楚。


6
投票

毫无疑问的意图是两种形式都有明确的定义,但措辞无法捕捉到这一点。

两种形式都将在实践中发挥作用

reinterpret_cast更明确的意图,应该是首选。


3
投票

这是真正的原因是因为C ++如何定义继承,以及成员指针。

使用C,指针几乎只是一个地址,应该是它。在C ++中,由于它的一些特性,它必须更复杂。

成员指针实际上是一个类的偏移量,所以使用C样式转换它们总是一个灾难。

如果你有多个继承了两个也有一些具体部分的虚拟对象,那么这也是C风格的一个灾难。但是,多重继承会导致所有问题,因此您无论如何都不应该使用它。

真的希望你从不首先使用这些案例。此外,如果你正在施展很多,这是另一个迹象,你正在弄乱你的设计。

我最终投出的唯一时间是C ++决定的区域中的原语是不一样的,但显然它们必须是。对于实际的对象,任何时候你想要投射某些东西,开始质疑你的设计,因为你应该在大多数时间“编程到界面”。当然,您无法更改第三方API的工作方式,因此您并不总是有太多选择。

© www.soinside.com 2019 - 2024. All rights reserved.