通过 void* 进行投射,而不是使用 reinterpret_cast [重复]

Posted

技术标签:

【中文标题】通过 void* 进行投射,而不是使用 reinterpret_cast [重复]【英文标题】:casting via void* instead of using reinterpret_cast [duplicate] 【发布时间】:2010-12-24 04:47:40 【问题描述】:

我在看书,发现reinterpret_cast不应该直接使用,而是结合static_cast强制转换为void*:

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

代替:

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

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

提前致谢

附言我知道reinterpret_cast 是用来做什么的,但我从来没有见过这样用的

【问题讨论】:

感谢您提出这个问题。似乎答案深埋在标准中。 (在usenet 中问过它几次,没有人能指出该静态转换序列做得更好的保证)。 必须有一些东西:C++0x 在reinterpret_cast 的规范中添加了一些措辞,当与某些类型一起使用时,它会将其重写为static_cast 序列。 @sinec 我不得不问你为什么觉得有必要进行这样的演员阵容?多年来,我已经编写了大量的 C++ 代码,而无需这样做。 @Neil Butterworth 我参与了一个项目(这更像是向现有代码添加新功能)并且有很多疯狂的演员,但这是不可避免的,因为我们无法更改遗留代码。无论如何,我在看书时问了这个问题,但我找不到对此的解释。 哪本书?无论如何,买一本更好的书。 本书是C++编码标准(Sutter/Alexandrescu)——类型安全部分(第91章)。 【参考方案1】:

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

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

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

【讨论】:

【参考方案2】:

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

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

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

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

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

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

【讨论】:

我同意你在这里所说的大部分内容,但是关于选角的负面污名可能有点多。强制转换有它的位置(就像大多数其他语言特性一样),基于最近的一些工作,我做了一些有用的例子,包括:在非常量运算符重载中使用 const_cast 来调用 const 重载(逻辑相同),调用转换运算符以防止代码重复,解决歧义(输出小整数文字与字符文字),并使用重载的地址运算符获取对象的真实地址。我猜每个功能都有一个季节。【参考方案3】:

对于允许进行此类强制转换的类型(例如,如果 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 数组的内容复制回对象,则该对象随后应保持其原始值。

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

3.9.2[basic.compound]:

cv-qualified (3.9.3) 或 cv-unqualified 类型的对象void*(指向 void 的指针),可用于指向未知类型的对象。 void* 应该能够保存任何对象指针。 cv-qualified 或 cv-unqualified (3.9.3) void* 应具有与 cv-qualified 或 cv-unqualified 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-struct 对象的指针,使用 reinterpret_cast 适当地转换,指向它的初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。 [注意:因此在 POD 结构对象内可能有未命名的填充,但不是在其开头,这是实现适当对齐所必需的。 ]

这似乎暗示了指针之间的reinterpret_cast 在某种程度上暗示了“相同的地址”。去搞清楚。

【讨论】:

但是对于static_cast 的结果,也不再保证转换为void* 并返回到一个不同的 类型。它只是说“指向对象的类型指针的值转换为“指向 cv void 的指针”并返回到原始指针类型将具有其原始值。” 查看编辑后的答案。没有明确声明“这没关系”,但标准中有许多参考资料强烈暗示它是这样的。特别注意,任何 POD 都由 char objects 组成,并且 static_cast'ing 指向 void* 的 POD 结构指针会生成一个指向第一个此类 char 对象的 void* 指针(因为不允许初始填充在 POD 结构中)。 即如果我们以某种方式分别获得指向对象表示的第一个字符的指针(没有 static_cast)并将其转换为void*,则标准间接要求它等于指向对象本身的指针转换为void*。从那以后,正如我们可以将前一个指针反向转换为 char* 并让它工作一样,我们可以将后一个指针“反向转换”到 char* 并让它也能工作(因为它是同一个指针价值!)。 ... 但是,另请参阅最近的编辑。这令人难以置信,我现在倾向于说,根据我之前的 cmets 的逻辑,static_castreinterpret_cast 实际上都可以保证工作。 -1 "标准中有许多引用强烈暗示它是这样的。" 所有这些引用甚至都没有提到static_cast

以上是关于通过 void* 进行投射,而不是使用 reinterpret_cast [重复]的主要内容,如果未能解决你的问题,请参考以下文章

使用不在 Chrome 中的应用程序进行投射

光线投射到 TextGeometry 的 boundingBox 而不是网格

我如何投射这个指针

在clickhouse中,投射失败时如何返回null而不是抛出异常?

Chromecast 是不是支持从 Android 设备进行屏幕投射?

通过 Chrome 的原生视频播放器以编程方式投射