动态类型转换规范(规则)澄清

5
我们有 dynamic_cast 的通用形式:

dynamic_cast < new-type > ( expression )

我特别困惑于此规则(5a)的加粗部分:

5:如果 expression 是指向或引用多态类型 Base 的指针或引用, 并且 new-type 是指向或引用类型 Derived 的指针或引用,则执行运行时检查:

a) 检查指针/标识符 expression 所指向/引用的最派生对象。 如果在该对象中,expression 指向/引用了 Derived 的公共基类, 并且只有一个派生自 expression 所指向/标识的子对象的 Derived 类型对象, 那么强制转换的结果将指向/引用该 Derived 对象。 (这称为“下行转换”)。

您能否给出一个不满足此部分的示例?
上面的摘录来自 cppreference:cppreference

您可以使用多重继承构建示例,在其中某些基类和派生自共同基类的类被某些具体类多次继承。在这种情况下,从“Base”向“Derived”的向下转换是不明确的。 - Peter
@YoungBajwa,好问题,我认为你应该补充一下这个提取是来自cppreference,并添加链接。 - Alessandro Teruzzi
@AlessandroTeruzzi 编辑并添加了注释和链接,感谢。 - YoungOne
1个回答

5

完善 @Peter 总结的多重继承示例:

     Base1
     /   \  <-- Virtual inheritance here
  Base2  Base2
    |     | <-- Nonvirtual inheritance here and below
  Left   Right
    \     /
    Derived

Base1* p_base1 = new Derived();
Base2* p_base2 = dynamic_cast<Base2*>(p_base1); // Which Base2?

在一个Derived对象中有两个不同的Base2对象,那么p_base2应该指向哪一个?

代码示例:

#include <iostream>

struct Base1 { virtual ~Base1() = default; };
struct Base2 : virtual Base1 { };
struct Left : Base2 { };
struct Right : Base2 { };
struct Derived : Left, Right {
    Derived() : Base1() {}
};

int main()
{
    Base1* p_base1 = new Derived();
    Base2* p_base2 = dynamic_cast<Base2*>(p_base1);
    std::cout << std::boolalpha;
    std::cout << "p_base1 == nullptr: " << (p_base1 == nullptr) << '\n';
    std::cout << "p_base2 == nullptr: " << (p_base2 == nullptr);
    delete p_base1;
}

这里需要小心一点: Base1 被虚继承,以便只有单个 Base1 的子对象,并且我们可以实际初始化 p_base1。但是,DerivedLeftRight 非虚拟继承,这意味着它有两个 Base2 实例。因此,下行转换失败。

p_base1 == nullptr: false
p_base2 == nullptr: true

谢谢提供的示例。我之前尝试过类似的方法,但是 dynamic_cast 仍然成功了,因此感到困惑。Base1* p_base1 = new Derived(); 会导致编译器错误,因为 Derived* -> Base1* 的转换是有歧义的。所以,我选择了 Derived->Left->Base2->Base1 的路径来获取指向 Base1 的指针,然后尝试了 dynamic_cast,它可以正常工作。https://godbolt.org/z/7ErE3M1av - YoungOne
@YoungBajwa,你说得对,原始答案不能让你编写Base1* p_base1 = new Derived();,但是将其更改为Derived直接从Base1继承,但仍存在哪个Base2的歧义会导致动态转换失败。 - Nathan Pierson
代码示例无法编译,因此我无法复制您的输出。https://godbolt.org/z/3z419eE93 - YoungOne
好奇心驱使我,我不确定为什么MSVC可以编译它,但GCC和Clang不能。它会发出警告,但不会报错。 - Nathan Pierson
这个图似乎描绘了虚拟继承。常规的继承通常被描绘成一棵树。 - rustyx
显示剩余4条评论

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