当涉及虚拟继承时,为什么不能使用static_cast进行向下转换?

era*_*ran 40 c++ virtual-inheritance downcast static-cast

请考虑以下代码:

struct Base {};
struct Derived : public virtual Base {};

void f()
{
    Base* b = new Derived;
    Derived* d = static_cast<Derived*>(b);
}
Run Code Online (Sandbox Code Playgroud)

这是标准([n3290: 5.2.9/2])禁止的,因此代码无法编译,因为Derived 实际上是继承自的Base.virtual从继承中删除使代码有效.

这条规则存在的技术原因是什么?

Ste*_*sop 36

技术问题是没有办法解决从子对象的开始和对象的开始Base*之间的偏移.BaseDerived

在你的例子中,它看起来没问题,因为只有一个类可以看到Base基数,因此继承是虚拟的,这似乎无关紧要.但是编译器不知道是否有人定义了另一个class Derived2 : public virtual Base, public Derived {},并且正在Base*指向它的Base子对象.通常[*],Base子对象和Derived子对象之间的偏移量Derived2可能与Base子对象和Derived最大派生类型的对象的完整对象之间的偏移量不同Derived,正是因为Base它实际上是继承的.

因此,根据动态类型的不同,无法知道完整对象的动态类型,以及给定转换的指针和所需结果之间的不同偏移量.因此演员阵容是不可能的.

Base没有虚函数,因此没有RTTI,所以当然没有办法告诉完整对象的类型.即使Base有RTTI(我不会立即知道原因),演员仍然被禁止,但我想如果没有检查dynamic_cast在这种情况下是否可行.

[*]我的意思是,如果这个例子不能证明这一点,那么继续添加更多的虚拟继承,直到找到偏移量不同的情况;-)

  • +1:让我的头受伤了,所以我不会说这是一个很明确的答案,但对我来说似乎是正确的答案。:) (2认同)
  • 这个解释不够清楚,看不懂。你能详细说明一下吗? (2认同)

Jam*_*nze 5

从根本上来说,没有真正的原因,但目的是 static_cast非常便宜,最多涉及到指针的常量加法或减法。而且没有办法以如此低廉的成本实现你想要的演员阵容;Derived基本上,因为如果有额外的继承,对象中和的相对位置Base可能会改变,所以转换将需要大量dynamic_cast;的开销。委员会成员可能认为这违背了使用static_cast 而不是的理由dynamic_cast

  • 从根本上来说,有_一切_原因。 (3认同)
  • @JamesKanze:我的意思几乎就是你所说的。会有运行时代码,但本质上会受到限制:检查空指针,添加在编译时确定的固定偏移量。静态转换无需 RTTI 即可工作。动态转换一般不会。(有一些重叠;动态转换可用于向上转换。) (3认同)

Cyg*_*sX1 5

static_cast只能执行在编译时知道类之间的内存布局的那些强制转换。dynamic_cast可以在运行时检查信息,从而可以更准确地检查转换的正确性,以及读取有关内存布局的运行时信息。

虚拟继承将运行时信息放入每个对象中,这些信息指定了Base和之间的内存布局Derived。是一个接一个的还是另外一个缺口?由于static_cast无法访问此类信息,因此编译器将采取保守措施,并仅给出编译器错误。


更详细地:

考虑一个复杂的继承结构,其中-由于多重继承-存在多个副本Base。最典型的情况是钻石继承:

class Base {...};
class Left : public Base {...};
class Right : public Base {...};
class Bottom : public Left, public Right {...};
Run Code Online (Sandbox Code Playgroud)

在这种情况下,Bottom包含LeftRight,其中每个都有自己的副本Base。上述所有类的存储器结构在编译时都是已知的,static_cast可以毫无问题地使用。

现在让我们考虑类似的结构,但具有以下事实的虚拟继承Base

class Base {...};
class Left : public virtual Base {...};
class Right : public virtual Base {...};
class Bottom : public Left, public Right {...};
Run Code Online (Sandbox Code Playgroud)

使用虚拟继承确保当Bottom被创建,它仅包含一个拷贝Base共享对象部分之间LeftRightBottom对象的布局可以是例如:

Base part
Left part
Right part
Bottom part
Run Code Online (Sandbox Code Playgroud)

现在,考虑您强制转换BottomRight(这是有效的强制转换)。您将获得一个Right指向对象的指针,该指针分为两部分:Base并且Right在两者之间有一个内存间隙,其中包含(现在不相关)Left部分。有关该间隙的信息在运行时存储在Right(通常称为vbase_offset)的隐藏字段中。您可以在此处阅读详细信息。

但是,如果仅创建一个独立的Right对象,则不会存在差距。

因此,如果我只是给您一个指向您的指针,Right则在编译时不知道它是独立对象还是更大对象(例如Bottom)的一部分。您需要检查运行时信息以正确地从转换RightBase。这就是为什么static_cast会失败而dynamic_cast不会失败的原因。


关于dynamic_cast的注意事项:

虽然static_cast不使用有关对象的运行时信息,但dynamic_cast使用并要求它存在!因此,后者的强制转换只能用于包含至少一个虚拟函数(例如,虚拟析构函数)的类上