Pergunta

I tried creating a class that inherits from multiple classes as followed, getting a "diamond"
(D inherits from B and C. B and C both inherits from A virtually):

  A
  / \
B   C
  \ /
  D

Now, I have a container with a linked list that holds pointers to the base class (A).
When I tried doing explicit casting to a pointer (after typeid check) I got the following error:
"cannot convert a pointer to base class "A" to pointer to derived class "D" -- base class is virtual"

But when I use dynamic casting it seems to work just fine.
Can anyone please explain to me why I have to use dynamic casting and why does the virtual inheritance causes this error?

Foi útil?

Solução

"Virtual" always means "determined at runtime". A virtual function is located at runtime, and a virtual base is also located at runtime. The whole point of virtuality is that the actual target in question is not knowable statically.

Therefore, it is impossible to determine the most-derived object of which you are given a virtual base at compile time, since the relationship between the base and the most-derived object is not fixed. You have to wait until you know what the actual object is before you can decide where it is in relation to the base. That's what the dynamic cast is doing.

Outras dicas

When I tried doing explicit casting to a pointer (after typeid check)

After a successful typeid(x) == typeid(T) you know the dynamic type of x, and you could in theory avoid any other runtime checking involved in dynamic_cast at that point. OTOH, the compiler is not required to do this kind of static analysis.

static_cast<T&>(x) does not convey to the compiler the knowledge that the dynamic type of x really is T: the precondition is weaker (that a T object has x as a subobject base class).

C++ could provide a static_exact_cast<T&>(x) which is only valid if x designates an object of dynamic type T (and not some type derived from T, unlike static_cast<T&>). This hypothetical static_exact_cast<T&>(x), by assuming the dynamic type of x is T, would skip any runtime check and compute the correct address from knowledge of T object layout: because in

D d;
B &br = d;

no runtime offset computation is necessary, in static_exact_cast<D&>(br) the reverse adjustment would involve no runtime offset computation.

Given

B &D_to_B (D &dr) {
  return dr;
}

a runtime offset computation is needed in D_to_B (except if whole program analysis shows that no class derived from D has different offset of base class A); given

struct E1 : virtual A
struct E2 : virtual A
struct F : E1, D, E2

the layout of the D subobject of F will be different from the layout of a D complete object: the A subobject will be at a different offset. The offset needed by D_to_B will be given by the vtable of D (or stored directly in the object); it means that D_to_B will not just involve a constant offset as a simple "static" upcast (before entering the constructor of the object, the vptr is not set up so such casting cannot work; be careful with up casts in constructor init list).

And BTW, D_to_B (d) is not different from a static_cast<B&> (d), so you see that the runtime computation of an offset can be done inside a static_cast.

Consider the following code compile naively (assuming no fancy analysis showing that ar has dynamic type F):

F f;
D &dr = f; // static offset
A &ar = dr; // runtime offset
D &dr2 = dynamic_cast<D&>(ar);

Finding the A base class subject from a reference to D (a lvalue of unknown dynamic type) requires a runtime check of the vtable (or equivalent). Going back to the D subobject requires a non trivial computation:

  • finding out the address of the complete object (which happens to be of type F), using the vtable of A
  • finding out the address of the unambiguous and publicly derived D base class subobject of f, using the vtable again

This is not trivial as dynamic_cast<D&>(ar) statically knows nothing specific about F (the layout of F, the layout of the vtable of F); everything is fetched from the vtable. All dynamic_cast knows is that there is a derived class of A and the vtable has all the information.

Of course, there is no static_exact_cast<> in C++, so you have to use dynamic_cast, with the associated runtime checks; dynamic_cast is a complex function, but the complexity covers the base class cases; when the dynamic type is given to dynamic_cast, the base classes tree walking is avoided, and the test is fairly simple.

Conclusion:

Either you name the dynamic type in dynamic_cast<T> and dynamic_cast is fast and simple anyway, or you don't and the complexity of dynamic_cast is really needed.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top