符合标准的编译器是否可以拒绝包含非多态类型向下转换dynamic_cast代码

Can a standard-compliant compiler reject code containing dynamic_cast downcast from non-polymorphic type?

本文关键字:类型 转换 dynamic 代码 cast 多态 包含非 标准 编译器 是否 拒绝      更新时间:2023-10-16

这个问题的灵感来自这里的评论。

请考虑以下代码片段:

struct X {}; // no virtual members
struct Y : X {}; // may or may not have virtual members, doesn't matter
Y* func(X* x) { return dynamic_cast<Y*>(x); }

有几个人建议他们的编译器会拒绝func的主体。

但是,在我看来,这是否由标准定义取决于x的运行时值。 从第 5.2.7 节 ( [expr.dynamic.cast] ):

  1. 表达式 dynamic_cast<T>(v) 的结果是 将表达式v转换为类型 TT应为指针或 引用完整的类类型,或"指向 CV void 的指针"。这 dynamic_cast经营者不得抛弃恒常性。

  2. 如果T是指针类型,则v应是指向完成类的指针的 prvalue 类型,结果是 T 类型的 prvalue 。如果T是左值 引用类型,v应为完整类类型的左值,并且 结果是 T 引用的类型的左值。如果T是右值 引用类型,v应为具有完整类类型的表达式, 结果是 T 所指类型的 x值。

  3. 如果 v的类型与T相同,或者与T相同,只是 T 中的类对象类型比类对象类型更符合 CV 条件 在 v 中,结果是 v(必要时转换)。

  4. 如果在指针大小写中 v 的值是空指针值,则结果是 T 类型的空指针值。

  5. 如果 T 是"指向 cv1 B 的指针"并且v具有类型 '指向 cv2 的指针D"使得 BD 的基类,结果是 指向 v 指向的D对象的唯一B子对象的指针。 类似地,如果 T 是"对 cv1 B 的引用",并且v的类型为 cv2 D使得 BD的基类,结果是D的唯一B子对象 v 引用的对象。如果结果是左值T则为左值 引用,或者如果 T 是右值引用,则为 xValue。在两者中 指针和引用案例,如果 CV2 具有,则程序格式不正确 比 CV1 更高的 CV 资格,或者如果 B 是 无法访问或 D的模棱两可的基类。

  6. 否则,v应是指向多态类型的指针或左值。

  7. 如果T是 "指向 cv void 的指针",则结果是指向最派生的指针 v指向的对象。否则,将应用运行时检查以查看 如果指向或引用的对象v可以转换为类型 由 T 指向或引用。最派生的对象指向 或由 v 引用,可以包含其他B对象作为基类,但 这些将被忽略。

  8. 如果CT指向或引用的类类型, 运行时检查按逻辑执行如下:

    • 如果,在最 派生对象指向(引用)由 vv 点(引用)指向 public C 对象的基类子对象,如果只有一个对象 类型 C 派生自指向(引用)的子对象,v 结果点(引用)到该C对象。

    • 否则,如果v分 (引用)派生最多的对象的public基类子对象, 并且派生最多的对象的类型有一个基类,类型为 C , 这是明确和public,结果指向(引用)C 派生最多的对象的子对象。

    • 否则,运行时检查 失败

  9. 失败的强制转换为指针类型的值为 null 所需结果类型的指针值。要引用的转换失败 类型抛出std::bad_cast .

我读到这里的方式,多态类型的要求仅在不满足上述条件的情况下适用,并且其中一个条件取决于运行时值。

当然,在少数情况下,编译器可以肯定地确定输入不能正确为 NULL(例如,当它是 this 指针时),但我仍然认为编译器不能拒绝代码,除非它可以确定该语句将被到达(通常是运行时问题)。

警告诊断在这里当然是有价值的,但是编译器拒绝此代码并出现错误是否符合标准?

一个非常好的观点。

请注意,在C++03中,5.2.7/3和5.2.7/4的措辞如下

3 如果 v 的类型与所需的结果类型相同(对于方便,在此描述中将称为R),或者相同作为 R,除了 R 中的类对象类型比 cv 更符合 cv 条件类对象类型在 V 中,结果为 V(必要时转换)。

4 如果 v 的值在指针大小写中为空指针值,则结果是类型 R 的空指针值。

5.2.7/3中提到的第R类似乎暗示5.2.7/4是5.2.7/3的一个子条款。换言之,5.2.7/4似乎只打算在5.2.7/3所述的条件下适用,即当类型相同时。

然而,C++11中的措辞有所不同,不再涉及R,这不再暗示5.2.7/3和5.2.7/4之间有任何特殊关系。不知道是不是故意改的...

我相信

这种措辞的意图是一些强制转换可以在编译时完成,例如upcasts或dynamic_cast<Y*>((X*)0),但其他转换需要运行时检查(在这种情况下需要多态类型。

如果您的代码片段格式正确,则需要运行时检查以查看它是否为 null 指针值,这与运行时检查只应针对多态情况进行的想法相矛盾。

请参阅 DR 665,其中阐明了某些强制转换在编译时格式不正确,而不是推迟到运行时。

对我来说

,这似乎很清楚。我认为当您错误地解释要求枚举是"否则如果......否则如果.." 类型的东西。

第(1)和(2)点简单地定义了静态输入和输出类型允许是什么,包括cv资格和lvalue-rvalue-prvalue等。所以这是微不足道的,适用于所有情况。

第(3)点很清楚,如果输入和输出类型相同(添加的cv限定符除外),那么转换是微不足道的(没有,或者只是添加了cv限定符)。

点 (4) 明确要求,如果输入指针为空,则输出指针也为空。这一点需要作为一项要求,而不是作为拒绝或接受强制转换(通过静态分析)的问题,而是作为强调这样一个事实的问题,即如果从输入指针到输出指针的转换通常需要偏移到实际指针值(在多重继承类层次结构下可以), 然后,如果输入指针为 null,则不得应用该偏移量,以保持指针的"null"性。这只是意味着在执行动态强制转换时,将检查指针是否为空,如果为 null,则生成的指针也必须具有 null 值。

第 (5) 点简单地指出,如果它是向上转换(从派生到基),则强制转换是静态解析的(相当于 static_cast<T>(v) )。这主要是为了处理上行转换格式良好的情况(如脚注所示),但如果转到 v 指向的最派生对象(例如,如果 v 实际指向具有多个基类的派生对象,其中类 T 出现不止一次),则可能会出现格式错误的强制转换)。换句话说,这意味着,如果它是向上转换,则静态执行,而无需运行时机制(从而避免了不应该发生的潜在故障)。在这种情况下,编译器应该像拒绝static_cast<T>(v)一样拒绝强制转换。

在第(6)点

中,"否则"显然直接是指第(5)点(当然也是指第(3)点的微不足道的情况)。意思是(连同点 (7)),如果强制转换不是向上转换(也不是标识转换(第 (3) 点)),那么它就是向下转换,应该在运行时解析,明确要求 (v) 类型是多态类型(具有虚函数)。

您的代码应被符合标准的编译器拒绝。对我来说,这是毫无疑问的。因为,演员阵容是向下投的,v的类型不是多态的。它不符合标准规定的要求。null-pointer 子句(点 (4))实际上与它是否被接受的代码无关,它只是与在强制转换中保留一个 null 指针值有关(否则,某些实现可能会做出(愚蠢的)选择仍然应用强制转换的指针偏移量,即使值为 null)。

当然,他们本可以做出不同的选择,并允许强制转换从基础到派生(即,没有运行时检查)表现为静态强制转换,当基本类型不是多态时,但我认为这打破了动态强制转换的语义,这显然是说"我想对这个强制转换进行运行时检查", 否则你不会使用动态投射!