钻石继承中的向下转型问题

23
为什么static_cast不能从虚基类进行向下转换?
struct A {};
struct B : public virtual A {};
struct C : public virtual A {};
struct D : public B, public C {};

int main()
{
  D d;
  A& a = d;
  D* p = static_cast<D*>(&a); //error
}  

g++ 4.5 说:

 error: cannot convert from base ‘A’ to derived type ‘D’ via virtual base ‘A’

解决方案是使用dynamic_cast?但是为什么呢?有什么理性依据吗?
--编辑--
下面有很好的答案。没有一个答案详细说明子对象和虚函数表如何被排序。以下文章为gcc提供了一些很好的例子:
http://www.phpcompiler.org/articles/virtualinheritance.html#Downcasting

3
即使使用dynamic_cast,您的示例也无法正常工作,除非类A至少包含一个虚成员函数。 - Björn Pollex
@Space_C0wb0y 当然可以使用dynamic_cast,但是类必须被声明为虚拟类。 - log0
1
您还可以明确指定转换时应采取的路径:D *p = static_cast<D *>(static_cast<B *>(&a)); - Simon Richter
@Simon 不,这是同样的问题。如果A是虚基类,static_cast将无法工作。 - log0
真的。抱歉,我今天脑子不太灵光,感觉有点傻。 - Simon Richter
2个回答

11

显而易见的答案是:因为标准规定了这样做。标准中静态转换 (static_cast) 的动机是它应该尽可能接近平凡——最多只需将指针与一个常量相加或相减。而针对虚基类的向下转换需要更复杂的代码:可能需要在虚表中添加额外的条目。(因为如果有进一步的派生,D 相对于 A 的位置可能会改变,所以需要比常量更复杂的东西。) 转换显然是可行的,因为当你在一个 A* 上调用一个虚函数,并且该函数在 D 中被实现时,编译器必须进行转换,但由于附加的开销被认为不适合 static_cast。 (假定在这种情况下使用 static_cast 的唯一原因是优化,因为通常首选 dynamic_cast。因此,当 static_cast 可能像 dynamic_cast 一样昂贵时,为什么要支持它呢。)


对我来说,这里没有动态信息的必要,因此如果有成本,成本应该是100%的编译时间。 - log0
2
你错了。'A'和'D'的相对位置取决于整个继承层次结构,正如其他回答者已经指出的那样。对于任何给定的最派生类,它是一个常数,但是对于给定的'A*',编译器无法在不访问动态信息的情况下知道最派生类。 - James Kanze
2
这个标准背后的动机是,static_cast 应该尽可能接近于平凡——最多只是将一个常量加或减到指针上。而向虚基类进行下行转换则需要更复杂的代码。 - Nawaz
1
@Ugo 当然没问题。最终减法的结果在编译时是不知道的;它们取决于 d 指向对象的实际类型(当然,如果 d 是空指针,则无法进行运算)。如果我有一个 struct E : D, virtual A { int i; },且 d 实际上指向 E,那么减法的结果将与指向 D 的情况不同。你可以试一下。 - James Kanze
我不同意动机是static_cast应该接近于平凡。这里并不是问题的平凡性,而是static_cast不依赖于任何运行时信息,并且要执行那个向下转换需要动态信息,因此需要使用dynamic_cast - David Rodríguez - dribeas
显示剩余4条评论

10

由于如果实际上对象的类型是E(派生自D),则A子对象相对于D子对象的位置可能与对象实际为D时不同。

如果您考虑从A到C的转换,则实际上已经发生了这种情况。当您分配C时,它必须包含A的实例,并且它位于某个特定的偏移量上。但是,当您分配D时,C子对象引用了随B一起提供的A实例,因此其偏移量不同。


我不太明白,请您能否再解释一下? - Björn Pollex
对我来说没什么意义:在这个例子中没有类型'E'。您是不是想说“...实际上是类型D...”? - C.J.
5
@Space_COwbOy,@C Johnson:问题在于虚继承意味着将会有一个A的子对象。如果您将struct E : virtual A, UnrelatedType, D {}添加到层次结构中,则可以获得指向E对象的D指针,但是从该指针到A的偏移量将取决于所指对象的类型。 - David Rodríguez - dribeas
1
@C Johnson:不,我是指从示例中派生出的其他类型。 - Jan Hudec
回答不错,但这个偏移量不能在编译时计算吗? - log0
显示剩余4条评论

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