37

我正在读一本书,我发现reinterpret_cast不应该直接使用它,而是将其转换为 void* 并结合static_cast

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

代替:

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

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

提前致谢

ps 我知道是干什么reinterpret_cast用的,但我从来没见过这样用的

4

3 回答 3

26

对于允许进行这种T1强制转换的类型(例如,如果是 POD 类型并且T2unsigned char),static_cast标准中明确定义了方法。

另一方面,reinterpret_cast它完全是实现定义的——你得到的唯一保证是你可以将指针类型转换为任何其他指针类型,然后再返回,你将得到原始值;而且,您可以将指针类型转换为足够大的整数类型以保存指针值(取决于实现,并且根本不需要存在),然后将其转换回来,您将获得原始值。

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

5.2.10[expr.reinterpret.cast]:

reinterpret_cast 执行的映射是implementation-defined。[注意:它可能会或可能不会产生与原始值不同的表示形式。] ...指向对象的指针可以显式转换为指向不同类型对象的指针。)除了转换类型的右值“指向 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 [基本类型]:

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

T 类型对象的对象表示是由 T 类型对象占用的 N 个 unsigned char对象的序列,其中 N 等于 sizeof(T)。

3.9.2[基本.复合]:

cv-qualified (3.9.3) 或 cv-unqualified 类型void*(指向 void 的指针)的对象可用于指向未知类型的对象。Avoid*应该能够保存任何对象指针。cv-qualified 或 cv-unqualified (3.9.3)void*应具有与 cv-qualified 或 cv-unqualified 相同的表示和对齐要求char*

3.10[基本.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:

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

这似乎暗示reinterpret_cast指针之间以某种方式暗示“相同的地址”。去搞清楚。

于 2009-12-07T21:40:00.600 回答
6

毫无疑问,其意图是两种形式都得到了很好的定义,但措辞未能体现这一点。

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

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

于 2011-12-13T08:17:49.853 回答
4

之所以如此,是因为 C++ 定义继承的方式以及成员指针。

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

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

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

真的希望你一开始就不要使用这些案例。此外,如果你铸造了很多,那是你在设计中搞砸的另一个迹象。

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

于 2009-12-08T01:03:56.903 回答