Multiple Inheritance
One confusing thing about C++ is that pointer casts involve arithmetic. You can see this in the following simple test program:
struct A { int a; }; struct B { float a; }; // C inherits from A and B struct C : A , B { int c; }; int main(void) { C c; printf("A: %p\n", (A*)&c); printf("B: %p\n", (B*)&c); printf("C: %p\n", (C*)&c); }
This program will print out two different values. The reason is obvious: You can cast an object to either of its superclasses, but two different values can't occupy the same bit of memory, so the addresses of the object interpreted as an instance of its two superclasses must differ.
Single inheritance is trivial to implement. It's quite common to do it in C; for example, the Berkeley Sockets API uses it for generic data. You have a structure, and then you append extra fields to that structure for the subclass. As long as you have a single inheritance chain, you only have to keep adding new fields at the end. Once you add multiple inheritance, the situation gets complicated.
In simple cases, we just end up with a more complex layout. For example, the layout in the earlier example may look something like this:
struct C { struct A; int c; struct B; }
When you cast from C to B, you're adding an offset to the pointer. This situation gets more complicated when you insert virtual base classes. If A and B share a common virtual base class, then casting from C to this base class is easy, but what happens if you cast an instance of C to either A or B and then cast to the base class?
The solution lies in the vtable. One of the entries in the vtable for A and B will be reserved for storing the offset to the superclass. The vtable referenced in the A or B structure inside an instance of C will contain different offsets, allowing this cast to work.
This is one of the things that I dislike the most about C++. A simple operationa pointer castcan have widely differing costs, depending on the types. Even ignoring the fact that casts are operators, and therefore can be overloaded, a pointer cast may involve looking up a chain of vtables and accessing a dozen cache lines, or (as with C) it may just provide a hint to the compiler and require no machine code.
These core features of C++ are fairly easy to implement in pure C. In part 2 of this series, we'll look at some of the slightly more dynamic parts of C++.