2

In the sample of During construction and destruction:

struct V {
    virtual void f();
    virtual void g();
};

struct A : virtual V {
    virtual void f(); // A::f is the final overrider of V::f in A
};
struct B : virtual V {
    virtual void g(); // B::g is the final overrider of V::g in B
    B(V*, A*);
};
struct D : A, B {
    virtual void f(); // D::f is the final overrider of V::f in D
    virtual void g(); // D::g is the final overrider of V::g in D

    // note: A is initialized before B
    D() : B((A*)this, this) 
    {
    }
};


B::B(V* v, A* a)
{
    f(); // virtual call to V::f (although D has the final overrider, D doesn't exist)
    g(); // virtual call to B::g, which is the final overrider in B 

    v->g(); // v's type V is base of B, virtual call calls B::g as before

    a->f(); // a’s type A is not a base of B. it belongs to a different branch of the
            // hierarchy. Attempting a virtual call through that branch causes
            // undefined behavior even though A was already fully constructed in this
            // case (it was constructed before B since it appears before B in the list
            // of the bases of D). In practice, the virtual call to A::f will be
            // attempted using B's virtual member function table, since that's what
            // is active during B's construction)
}

Question 1: Why does v->g() calls B::g() ?
Question 2: What does this mean?

the virtual call to A::f will be attempted using B's virtual member function table, since that's what is active during B's construction.

rsy56640
  • 299
  • 1
  • 3
  • 13
  • 2
    This was my first idea as well but the whole sample is very convoluted (or even contrived). So, I'm not that sure anymore. May be, it helps to see this in action: [**Live Demo on coliru**](http://coliru.stacked-crooked.com/a/223ed7a1ec38433c) – Scheff's Cat Aug 23 '18 at 11:57
  • @user1810087 It does not make sense. Inside the constructor for `B` there is no `A` object anymore so what `A` does or does not override is irrelevant. – curiousguy Aug 25 '18 at 03:00

1 Answers1

2

In C++, accessing an unconstructed object is undefined. To avoid such undefined behavior, the object points to different virtual tables (vtable) during construction. If there are Base and Derived class, then the objects initially points to the vtable Base. Later when Derived starts construction, then the vtable points to `Derived. This answer explains it at the end together with the answer for question 2.

The same rule applies to virtual inheritance. However, in case of virtual inheritance the order of construction is different than with regular inheritance, and the vtable follows that construction order.

In your case you have the lines:

B::B(V* v, A* a)

and

D() : B((A*)this, this)  // in class D

This means that before D is constructed, it constructs its parent B. B::B gets this casted to A* and D*. At the time of B::B, the constructor of D did not start, so the vtable does not point to D's methods. The object points to an earlier vtable. The question which vtable the objects uses depends on the order of construction of the parts.

The virtual bases are constructed first, which is only V in this case. The rest is as usual. Which means that the order is V->A->B->D.

Here is a list of the different function calls in the code:

  • f() - D has not been constructed yet, so D::f() can't be called, and the vtable does not point to it. A is not a base of B so A::f() won't be called. The only option left is V::f(). Note that given a pointer to object, virtual function of siblings are never called. Only the most derived method of the object, its parents, and its children (all the way up to the dynamic type of the object).
  • g() - D has not been constructed yet, so D::g() can't be called. Since this is a constructor of B, it can access all its methods, so B::g() is called.
  • v->g() - v is of type V and might invoke g() of one of the subclasses through the virtual method mechanism. D has not been constructed yet, so D::g() is not yet in the vtable . Since this is a constructor of B, the vtable has been updated to point to B's methods and all the parts that have already been constructed (A and V). So, the vtable points to B::g().
  • a->f() - a is of type A, so it might call methods of its parent, but not of its subclass D because it has not been constructed yet. This means V::f() or A::f(). Since the virtual method invocation prefers the most derived method, then A::f() should be invoked.

I have answered the first original question of:

Why does v->g() calls B::g()?

and more.

For the second question, about what the following means:

the virtual call to A::f will be attempted using B's virtual member function table, since that's what is active during B's construction.

The above text talks about the conceptual model of virtual function calls. A virtual function call is as-if through an array of pointers to methods, which is called a "virtual table", the vtable. For every virtual function, such as f() in your example, the compiled code fetches the pointer-to-method from this vtable. This is very cheap since it is a simple index access into an array. Objects of the derived class have a different vtable, where some method pointers are different.

Code that gets a pointer to parent, does not care if it is the parent or the child. It simply gets the method-pointer from a given index in the vtable at O(1), regardless of the true type of the object. Changing the type in the constructor, from parent class to child class, happens by simple constant-time reassignment of a pointer to the vtable. The quoted text refers to the sequence of events that makes the pointer point to different vtables.

With virtual inheritance there can be more than one vtable active at a given time. The vtable is selected depending on the type through which the method call is performed (e.g. through B, A, or V).

The wording of the text talks about call to A::f using B's vtable, makes no sense. Even the example code says that in B::B's calling to f() invokes V::f() not A::f(). I think that the text should have been V::f() is performed though B's vtable, which is in line with what I wrote.

Michael Veksler
  • 8,217
  • 1
  • 20
  • 33
  • "_case of virtual inheritance the order of construction is different than with regular inheritance_" The order is what is different from what? – curiousguy Aug 25 '18 at 02:50
  • @curiousguy different from non-virtual inheritance – Michael Veksler Aug 25 '18 at 04:46
  • What would be the order with NV inheritance? – curiousguy Aug 25 '18 at 05:16
  • @curiousguy In this case slightly different V->A>V->B->D. In more complicated cases it can be much more. Regular inheritance construction is a simple DFS, where every subclass constructor takes parameters from its direct parent. With virtual inheritance the parameters are takes from earlier ancestor. – Michael Veksler Aug 25 '18 at 05:38
  • I just found a similar question on SO: https://stackoverflow.com/q/10534228/4955498 . I think this is a dup – Michael Veksler Aug 25 '18 at 05:40
  • So with ordinary inheritance, with that example, it's the number of bases which is changed, not the order. – curiousguy Aug 25 '18 at 05:54
  • @curiousguy if A had another non-virtual superclass X before V, then in regular inheritance X would have been constructed first, and with virtual inheritance V will come before X. – Michael Veksler Aug 25 '18 at 06:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/178764/discussion-between-curiousguy-and-michael-veksler). – curiousguy Aug 25 '18 at 22:27