C++什么时候应该优先使用两个链接的static_cast而不是reinterpret_cast?

8

3
对我来说,reinterpret_cast就像是设计不良(或者我做错了什么)的信号。我的建议是:不要使用reinterpret_cast。 - den bardadym
2
@den bardadym - 我过去为硬件设备编写了很多底层代码...如果不使用reinterpret_cast,我是无法完成这项工作的。我知道你的意思,在大多数情况下(除了底层),这可能是正确的,但并非普遍适用。 - 0xbadf00d
6个回答

9

reinterpret_cast 应该是一个巨大的闪烁符号,上面写着“这看起来很疯狂,但我知道我在做什么”。不要只因懒惰而使用它。

reinterpret_cast 的意思是“把这些位视为...”。链式静态转换并不相同,因为它们可能会根据继承格子修改它们的目标。

struct A {
    int x;
};

struct B {
    int y;
};

struct C : A, B {
    int z;
};

C c;
A * a = &c;

int main () {
    assert (reinterpret_cast <B *> (a) != static_cast <B *> (static_cast <C *> (a)));
}

如果您不能100%确定a指向b,请使用dynamic_cast来查找上述解决方案(尽管会有运行时成本)。请记住,这可能返回NULL或在失败时抛出异常。
我试图想到我实际使用reinterpret_cast的时候,只有两个:
- 当一个函数正在压缩/加密任意缓冲区并且我想使用const char *来遍历它时 - if(*reinterpret_cast<uint32_t*>(array_of_4_bytes_A) < *reinterpret_cast<uint32_t*>(array_of_4_bytes_B) 或类似的行。像这样的行邀请审查并要求注释。
否则,如果您有一个实际上是B*A*,那么您可能需要一个union。

2
请注意,通过 void* 进行两个 static_cast 的代码与 reinterpret_cast 一样疯狂,但是您不再有巨大的闪烁符号。 如果您认为这很疯狂,那么您应该保留疯狂警告。 如果您认为这不疯狂,则应删除疯狂警告。 - Steve Jessop
2
更疯狂一些。那个闪烁的疯狂警告非常重要。不要隐藏它。 - David Hammen
@spraff - 当然,除非你绝对确定当前类型的 指向A的指针 实际上是 指向C的指针,否则不应该使用 reinterpret_cast 来处理对象指针。然而,这仍然是一个糟糕的设计。 - 0xbadf00d
在C++0x中没有受限制的联合体之前,有一些合理的情况可以使用(通常涉及到放置new),但你必须小心。http://www2.research.att.com/~bs/C++0xFAQ.html#unions - spraff

5
我个人更喜欢看到 reinterpret_cast <TargetType> (pointer_of_some_other_type) 而不是 static_cast <TargetType> (static_cast <void*> (pointer_of_some_other_type)) 或者 static_cast <TargetType> ((void*) (pointer_of_some_other_type))。通过 void* 的转换链只是一种隐蔽、卑鄙的方式,可以避免使用可怕的 reinterpret_cast。
许多项目禁止使用 reinterpret_cast,除非获得豁免;编写代码的人需要证明使用此转换的必要性。在我看来,静态转换链比 reinterpret_cast 更糟糕(要糟糕得多!)。这个链具有与 reinterpret_cast 相同的影响和问题,但该链没有使用 grep 容易找到的好处。
补充说明 这样看待它。情况1,您使用 reinterpret_cast,通过所有项目审核来证明其使用的必要性,项目经理批准豁免。几个月后,一个错误被追溯到您使用 dynamic_cast。你有一张出狱卡。是项目经理为给你那张卡而承担风险。
情况2,您使用阴险卑鄙的静态转换链,代码通过同行评审。几个月后,一个错误被追溯到您使用卑劣的技巧。您的项目经理可能因未能发现这种恶意而陷入麻烦,但是你会受到惩罚。你没有那张出狱卡。你不过关,直接去排队领取失业救济。

说得好。我想知道那个匿名的莫名其妙的踩贴者是谁。 - Cheers and hth. - Alf
1
“much worse” - 尽管在极少数情况下,两个静态转换的结果是严格别名安全和定义良好的,但 C++03 标准存在一个缺陷,即 reinterpret_cast 的结果是未指定的。这很遗憾,但正如 Alf 所说,在实践中不需要担心这个问题。因此,您可以决定 (a) 在我们的风格中,reinterpret_cast 是否是一个大而醒目的标志,如果是的话 (b) 将一个指向 POD 的指针转换为 char* 的第一个字节是否适合使用大而醒目的标志?如果是“是,否”,则使用静态转换,或者至少有一个快捷方式自动获取豁免权。 - Steve Jessop
例如,避免不必要的麻烦的一种方法可能是使用boost:is_pod编写自己的转换样式模板函数,以执行被视为安全的转换。然后,该模板使用reinterpret_cast还是static_cast都无关紧要,因为它是一个经过精心构建的用途,公司中没有其他人需要编写任何转换,他们可以使用该函数。说实话,从void *进行static_cast本来就应该受到怀疑,如果我要解雇程序员,那么我必须在良心上解雇PM和代码审查员。 - Steve Jessop
你的强制类型转换模板想法是一个不错的解决方法。该模板可以经过深入审查,以确保其正常工作。在我的世界中,编程标准通常不是硬性规定。有时可以获得豁免(有时候容易,有时候不容易,有时候在项目结束后15个月才能获得)。例如,mutable是一个禁用的关键字。为声明互斥量为mutable获得豁免是小菜一碟。volatile也是一个禁用的关键字。为使用volatile获得豁免则更加棘手和不确定。 - David Hammen
“有时在项目结束后15个月” - 没有我担心的那么糟糕。如果你被允许这样做(尽力而为),但将其标记为未来确认或更改的已知问题,那么比代码在初始审查中被搁置直到所有利益相关者都被咨询要好得多。一旦代码可用于测试,这些利益相关者可以自行决定是否能够在发布之前抽出时间查看它 :-) - Steve Jessop
显示剩余3条评论

2

在处理类型转换时,reinterpret_cast 应该是最后的选择 - 因为它不会进行任何检查!- 所以如果你能够将两个、三个或十个语句链接起来,对操作进行某种形式的验证,那么你就获得了有价值的东西。


2

由于场景清单可能会很长,因此我用简单的话来表达:

如果链接的static_cast<>不会导致编译错误,则应避免使用reinterpret_cast<>


喊叫并不能使事情更正确。这只是当前标准中微小的遗漏缺陷,已经被修复了。Andrei和Sutter基于这个遗漏写了指南,但由于问题已经被解决,而且没有编译器会按照那个指南做错事情,因此遵循那个指南就像是在假装一个非连续字符串缓冲区,或者其他纯形式上的问题一样愚蠢。 - Cheers and hth. - Alf
@Alf P. Steinbach,喊叫并非有意。 :) - iammilind
2
我强烈反对。通过void*进行链接静态转换比reinterpret_cast要糟糕得多。给你的项目经理那个巨大的闪烁符号。经理和代码审查人员会看到它,并可能提出如何避免它的建议。链接静态转换隐藏了应该供所有人看到的大闪烁标志。 - David Hammen
@David Hammen - 我完全同意你的观点。从技术上讲,reinterpret_cast 和链式 static_cast 是相同的。你所获得的唯一“好处”就是隐藏你实际上正在做什么。 - 0xbadf00d
你强调的那一行只有在不经过void*的情况下才是正确的。 - spraff

1

在处理跨类型指针时,不应该使用 reinterpret_cast - 而是应该使用隐式转换到void*,然后再使用static_cast


为什么在这里使用隐式转换是一个好选择?据我所知,使用隐式转换和使用reinterpret_cast一样不好,但也许你有使用void*时使用它的好理由? - KillianDS
1
@KillianDS:我已更改链接,请查看相关问题。 - sharptooth
哦,你链接的讨论大多是胡言乱语。看起来正式但实际上并不是。请不要传播这些城市传说:它们只会增加很多工作,但没有任何好处。 - Cheers and hth. - Alf

0

因为从理论上讲,它们可以做一些不同的事情(尽管很难想象这样的情况)。更重要的是,它们向读者发送不同的信号,并向编译器讲述不同的故事(这可能会影响优化)。从逻辑上讲,我建议在处理字符类型(例如转储原始内存图像)和其他已定义且可移植的情况时使用链接的 static_cast 通过 void*,并在进行真正的低级别、依赖硬件的工作时使用 reinterpret_cast,例如通过将其地址强制转换为 unsigned int* 并进行位掩码操作来提取浮点数的指数字段。


1
请举一个“他们可以做些不同的事情”的例子(不仅仅是C++98缺少对void* reinterpret_cast的支持的例子)? - Cheers and hth. - Alf
就我个人而言,我更愿意通过reinterpret_cast而不是static_cast将任何从void*到其他类型的转换完成。项目经理有权知道这种荒唐行为何时发生。事实上,在我对编码标准有一定发言权的项目中,这可能成为一项规则。(C++98不支持这一点已经过时了。) - David Hammen
@Alf 什么样的例子。双重 static_cast 的语义由标准定义,并且保证在特定情况下工作。而 reinterpret_cast 所做的是实现定义的,实现有时会变得反常。实际上,在这种情况下,我偶尔会使用 reinterpret_cast,尽管它引发了不适当的警告,因为实际上它是有效的,即使在理论上可能不是这样。 - James Kanze
@FrEEzE2046 在我的副本中,5.2.10/7 写道:“对象的指针可以显式转换为不同类型的对象的指针。除了将类型为“指向 T1 的指针”的 rvalue 转换为类型为“指向 T2 的指针”(其中 T1 和 T2 是对象类型,并且 T2 的对齐要求不比 T1 更严格)并返回其原始类型的结果是原始指针值之外,此类指针转换的结果是未指定的。” 你可能正在查看标准的未来版本。 - James Kanze
1
@James Kanze - 对不起,你是正确的。我引用了最新的C++0x草案。 - 0xbadf00d
显示剩余2条评论

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