understanding of multiple inheritance for c++

214 views Asked by At

I'm reading multiple inheritance for c++ An Example in the paper:(page 377)

class A {virtual void f();};
class B {virtual void f(); virtual void g();};
class C: A, B {void f();};
A* pa = new C;
B* pb = new C;
C* pc = new C;
pa->f();
pb->f();
pc->f();
pc->g()

(1) Bjarne wrote: On entry to C::f, the this pointer must point to the beginning of the C object (and not to the B part). However, it is not in general known at compile time that the B pointed to by pb is part of a C so the compiler cannot subtract the constant delta(B). So we have to store the delta(B) for the runtime which is actually stored with the vtbl. So the vtbl entry now looks like:

struct vtbl_entry {
    void (*fct)();
    int  delta;
}

An object of class c will look like this:

----------             vtbl: 
   vptr -------------->-----------------------
   A part                C::f   | 0 
----------             -----------------------   
   vptr -------------->----------------------- 
   B part                C::f   | -delta(B)
----------               B::g   | 0
   C part              -----------------------
----------

Bjarne wrote:

pb->f() // call of C::f:
register vtbl_entry* vt = &pb->vtbl[index(f)];
(*vt->fct)((B*)((char*)pb+vt->delta)) //vt->delta is a negative number I guess

I'm totally confused here. Why (B*) not a (C*) in (*vt->fct)((B*)((char*)pb+vt->delta))???? Based on my understanding and Bjarne's introduction at the first sentence at 5.1 section at 377 page, we should pass a C* as this here!!!!!!

Followed by the above code snippet, Bjarne continued writing: Note that the object pointer may have to be adjusted to po int to the correct sub-object before looking for the member pointing to the vtbl.

Oh, Man!!! I totally have no idea of what Bjarne tried to say? Can you help me explain it?

3

There are 3 answers

3
Puppy On BEST ANSWER

It is a C*, it's just not typed as such.

Frankly, that's a pretty terrible explanation and not really how it's done. It's a lot better and easier to store function pointers in the vtable.

struct vtbl {
    void(*f)(B* b);
};
struct B {
   vtbl* vtable;
};
// Invoke function:
B* p = init();
p->vtable->f(p);
// Function pointer points to:
void f_thunk(B* b) {
    C* c = (char*)b - delta(B);
    C::f(c);
}

When the compiler generates the thunks, it knows the derived object they're thunking to, so they don't need to store the offset in the vtable. The compiler can simply offset the pointer inside the thunk and then delegate to the appropriate method with the pointer. Of course, this thunk is pretty much generated assembly only without any C++ representation, so it would be invalid to state that that the pointers within have any particular C++ type.

0
R Sahu On

I'm totally confused here. Why (B*) not a (C*) in (*vt->fct)

At that level, the only known type is B. The actual object could be of type C, Foo, or Bar.

However, that paper is a bit dated. Actual implementations in modern compiler could be very different. @Puppy's answer shows how it can be done without adding delta(B) to the vtable.

3
Sam Varshavchik On

You're getting lost in the weeds here.

Are you trying to write your own C++ compiler? If so, feel free to ignore me. But if you're just trying to understand and learn C++ virtual inheritance, which is what it sounds like, none of what you wrote matters much.

Only compiler writers need to fully understand and figure out how the vtbl works, in all the gory details, in order to actually implement C++. It is not needed to effectively program and develop in C++. All that needs to be understood is how virtual inheritance works, from purely the class's viewpoint. As long as you understand that when invoking pb's method you're actually ending up invoking C's method, and why (with the why being simply "because it is actually an instance of C), that's pretty much all that needs to be understood.

Oh, and your class should probably have a virtual destructor, but that's a different story.

The vtbl is typically not even accessible by C++ code. And the C++ standard does not even require a C++ compiler to actually implement anything that's called "vtable". The only requirement is a specification for how virtual inheritance, and virtual method calls must work. Any actual implementation that produces the same results is compliant.