为什么涉及虚拟继承时不能使用static_cast向下转型?
考虑以下代码:
struct Base {};
struct Derived : public virtual Base {};
void f()
{
Base* b = new Derived;
Derived* d = static_cast<Derived*>(b);
}
这是标准所禁止的 ([n3290: 5.2.9/2]
),因此代码无法编译,因为 Derived
virtually< /em> 继承自Base
。从继承中删除virtual
会使代码有效。
该规则存在的技术原因是什么?
Consider the following code:
struct Base {};
struct Derived : public virtual Base {};
void f()
{
Base* b = new Derived;
Derived* d = static_cast<Derived*>(b);
}
This is prohibited by the standard ([n3290: 5.2.9/2]
) so the code does not compile, because Derived
virtually inherits from Base
. Removing the virtual
from the inheritance makes the code valid.
What's the technical reason for this rule to exist?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
技术问题是无法从
Base*
计算出Base
子对象的开头与的开头之间的偏移量是多少派生对象。
在您的示例中,它看起来没问题,因为只能看到一个带有
Base
基的类,因此继承是虚拟的似乎无关紧要。但编译器不知道是否有人定义了另一个class Derived2 :public virtual Base, public Derived {}
,并且正在强制转换指向Base 的
的子对象。一般来说[*],Base*
Derived2
中的Base
子对象和Derived
子对象之间的偏移量可能与Derived2
之间的偏移量不同。 code>Base 子对象和最派生类型为Derived
的对象的完整Derived
对象,正是因为Base
实际上是遗传。因此,无法知道完整对象的动态类型,以及您给出的强制转换指针与所需结果之间的不同偏移量,具体取决于动态类型是什么。因此演员阵容是不可能的。
你的
Base
没有虚函数,因此没有RTTI,所以当然没有办法告诉完整对象的类型。即使Base
确实有 RTTI(我不立即知道为什么),转换仍然被禁止,但我猜想在这种情况下没有检查dynamic_cast
是否可能。[*] 我的意思是,如果这个例子不能证明这一点,那么继续添加更多虚拟继承,直到找到偏移量不同的情况;-)
The technical problem is that there's no way to work out from a
Base*
what the offset is between the start of theBase
sub-object and the start of theDerived
object.In your example it appears OK, because there's only one class in sight with a
Base
base, and so it appears irrelevant that the inheritance is virtual. But the compiler doesn't know whether someone defined anotherclass Derived2 : public virtual Base, public Derived {}
, and is casting aBase*
pointing at theBase
subobject of that. In general[*], the offset between theBase
subobject and theDerived
subobject withinDerived2
might not be the same as the offset between theBase
subobject and the completeDerived
object of an object whose most-derived type isDerived
, precisely becauseBase
is virtually inherited.So there's no way to know the dynamic type of the complete object, and different offsets between the pointer you've given the cast, and the required result, depending what that dynamic type is. Hence the cast is impossible.
Your
Base
has no virtual functions and hence no RTTI, so there certainly is no way to tell the type of the complete object. The cast is still banned even ifBase
does have RTTI (I don't immediately know why), but I guess without checking that adynamic_cast
is possible in that case.[*] by which I mean, if this example doesn't prove the point then keep adding more virtual inheritance until you find a case where the offsets are different ;-)
static_cast
只能执行那些在编译时已知类之间的内存布局的转换。dynamic_cast
可以在运行时检查信息,这样可以更准确地检查转换的正确性,并读取有关内存布局的运行时信息。虚拟继承将运行时信息放入每个对象中,该信息指定
Base
和Derived
之间的内存布局。是一个接着一个还是有一个额外的间隙?由于static_cast
无法访问此类信息,因此编译器将采取保守的做法,仅给出编译器错误。更详细地说:
考虑一个复杂的继承结构,其中 - 由于多重继承 - 存在
Base
的多个副本。最典型的场景是菱形继承:在此场景中,
Bottom
由Left
和Right
组成,其中每个都有它自己的Base
副本。上述所有类的内存结构在编译时都是已知的,并且可以毫无问题地使用static_cast
。现在让我们考虑类似的结构,但具有
Base
的虚拟继承:使用虚拟继承可确保在创建
Bottom
时,它仅包含一个Base
的副本,在对象部分Left
和Right
之间共享。例如,Bottom
对象的布局可以是:现在,考虑将
Bottom
强制转换为Right
(这是有效的强制转换)。您获得一个指向对象的Right
指针,该对象分为两部分:Base
和Right
之间有一个内存间隙,包含(现在-不相关)Left
部分。有关此间隙的信息在运行时存储在Right
的隐藏字段中(通常称为vbase_offset
)。您可以在此处阅读详细信息。但是,如果您只创建一个独立的
Right
对象,则间隙将不存在。因此,如果我只给您一个指向
Right
的指针,您在编译时不知道它是一个独立对象,还是更大对象的一部分(例如Bottom
)。您需要检查运行时信息以正确从Right
转换为Base
。这就是为什么static_cast
会失败而dynamic_cast
不会。关于dynamic_cast的注意事项:
虽然
static_cast
不使用有关对象的运行时信息,但dynamic_cast
使用并且需要它存在!因此,后一种强制转换只能用于那些至少包含一个虚拟函数(例如虚拟析构函数)的类static_cast
can perform only those casts where memory layout between the classes is known at compile-time.dynamic_cast
can check information at run-time, which allows to more accurately check for cast correctness, as well as read run-time information regarding the memory layout.Virtual inheritance puts a run-time information into each object which specifies what is the memory layout between the
Base
andDerived
. Is one right after another or is there an additional gap? Becausestatic_cast
cannot access such information, the compiler will act conservatively and just give a compiler error.In more detail:
Consider a complex inheritance structure, where - due to multiple inheritance - there are multiple copies of
Base
. The most typical scenario is a diamond inheritance:In this scenario
Bottom
consists ofLeft
andRight
, where each has its own copy ofBase
. The memory structure of all the above classes is known at compile time andstatic_cast
can be used without a problem.Let us now consider the similar structure but with virtual inheritance of
Base
:Using the virtual inheritance ensures that when
Bottom
is created, it contains only one copy ofBase
that is shared between object partsLeft
andRight
. The layout ofBottom
object can be for example:Now, consider that you cast
Bottom
toRight
(that is a valid cast). You obtain aRight
pointer to an object that is in two pieces:Base
andRight
have a memory gap in between, containing the (now-irrelevant)Left
part. The information about this gap is stored at run-time in a hidden field ofRight
(typically referred to asvbase_offset
). You can read the details for example here.However, the gap would not exist if you would just create a standalone
Right
object.So, if I give you just a pointer to
Right
you do not know at compile time if it is a standalone object, or a part of something bigger (e.g.Bottom
). You need to check the run-time information to properly cast fromRight
toBase
. That is whystatic_cast
will fail anddynamic_cast
will not.Note on dynamic_cast:
While
static_cast
does not use run-time information about the object,dynamic_cast
uses and requires it to exist! Thus, the latter cast can be used only on those classes which contain at least one virtual function (e.g. a virtual destructor)从根本上来说,没有真正的原因,但目的是
static_cast
非常便宜,最多涉及一个添加或一个常量与指针的减法。并且没有办法
便宜地实现你想要的演员阵容;基本上,因为
对象内
Derived
和Base
的相对位置可能会改变如果有额外的继承,则转换需要良好的
处理
dynamic_cast
的开销;委员会成员可能认为这违背了使用
static_cast
的原因而不是
dynamic_cast
。Fundamentally, there's no real reason, but the intention is that
static_cast
be very cheap, involving at most an addition or asubtraction of a constant to the pointer. And there's no way to
implement the cast you want that cheaply; basically, because the
relative positions of
Derived
andBase
within the object may changeif there is additional inheritance, the conversion would require a good
deal of the overhead of
dynamic_cast
; the members of the committeeprobably thought that this defeats the reasons for using
static_cast
instead of
dynamic_cast
.考虑以下函数
foo
:虽然不是真正可移植的,但该函数向我们显示了 A 和 B 的“偏移量”。由于编译器在继承的情况下可以非常自由地放置 A 子对象(还请记住最派生的对象调用虚拟基构造函数!),实际放置取决于对象的“真实”类型。但由于 foo 只获得对 B 的引用,任何 static_cast (在编译时最多通过应用一些偏移量来工作)必然会失败。
ideone.com (http://ideone.com/2qzQu) 的输出为:
Consider the following function
foo
:Although not really portable, this function shows us the "offset" of A and B. Since the compiler can be quite liberal in placing the A subobject in case of inheritance (also remember that the most derived object calls the virtual base ctor!), the actual placement depends on the "real" type of the object. But since foo only gets a ref to B, any static_cast (which works at compile time by at most applying some offset) is bound to fail.
ideone.com (http://ideone.com/2qzQu) outputs for this:
static_cast
是一个编译时构造。它在编译时检查强制转换的有效性,如果强制转换无效则给出编译错误。虚拟主义是一种运行时现象。
两者不能在一起。
C++03 标准 §5.2.9/2 和 §5.2.9/9 与本例相关。
static_cast
is a compile time construct. it checks for the validity of cast at compile time and gives an compilation error if invalid cast.virtual
ism is a runtime phenomenon.Both can't go together.
C++03 Standard §5.2.9/2 and §5.2.9/9 ar relevant in this case.
我想,这是由于具有虚拟继承的类具有不同的内存布局。父级必须在子级之间共享,因此只能连续布置其中之一。这意味着,不能保证您能够分离连续的内存区域以将其视为派生对象。
I suppose, this is due to classes with virtual inheritance having different memory layout. The parent has to be shared between children, therefore only one of them could be laid out continuously. That means, you are not guaranteed to be able to separate a continuous area of memory to treat it as a derived object.