Your answer in your question: "Shared vtables between classes of the same name ...".
You have compiled one binary file from two cpp files, but each cpp file includes a different header file and, in particular, another struct base definition. In C ++, you cannot have two classes with the same name. If the same name is used, then they are the same class, and you must be consistent. (The obvious exception is putting them in two different namespaces.)
(Everything here is specific to the compiler. But this is probably the typical approach for most compilers.)
First, let's understand non-virtual methods. When you execute this method for an object:
b.foo(3);
the code is basically rewritten as follows, as if it were a regular free function:
foo_(b,3);
using the method implemented as follows:
void foo_(base * this, int i) { ... }
i.e. this pointer is "secretly" passed as the first parameter to the function.
But everything is not so simple with virtual methods. There will be two different free features that implement get . We will call one of them get_base , and the other get_derived . (It doesn't matter that you really have a pure virtual method ( =0 ), it really does not change the history.)
The question is, how is the correct get selected at run time for execution? Well, for every class that has at least one virtual method, the compiler creates a vtable. The vtable for this class lists all the virtual methods in this class. for instance
struct vtable_for_base_t { wchar_t* (*get_function_pointer)(base *);
A function pointer type is a function that takes one parameter (a base* , which becomes this ) and returns wchar_t* .
The two base and derived classes actually have pointers to these vtables under the hood.
struct base { vtable_for_base_t * vtable; .... other members of base }; struct derived { vtable_for_????_t * vtable; .... other members of derived };
Whenever a base object is created, the vtable pointer is initialized to point to the vtable for the base . Whenever a derived object is created, it instead points to a vtable for derived . Now that the compiler sees b.get() , it will change this to the next
(b.vtable->get_function_pointer)(&b);
It looks for the vtable pointed to by the b object to get a pointer to the correct version of get to use. And then it passes b this function to make sure it has the correct this pointer.
Thus, each object has a (hidden) element that knows the correct version of virtual functions. In this case, the compiler assumes that the first entry in the vtable for the base, as well as vtable for any type received from the base, will be the get method.
When building vtables for derived classes, the first entries will correspond to the methods that were in the base class. And they will be in the same order as in the database. Any new virtual methods in the derived class will be listed later.
If you had two virtual methods: foo and bar , in base , then these will be the first two entries in the vtable for base , and the corresponding versions for derivatives will also occupy the first two slots in the vtable for derived .
Now, to understand why you get segfault. In mod2.h , a vtable is created for the database, where data is the first (and only) record. Therefore, any code that includes mod2.h and that b.data() trying to execute b.data() execute the first entry in the vtable. But it doesn’t matter when modtest.cpp compiled, because instead it includes mod1.h
modtest.cpp includes mod1.h As a result, he sees the base class, which has two methods, where data is the second method specified in the vtable. Therefore, any attempt to execute b.data will actually become:
(b.vtable.SECOND_ENTRY)(&b);
since it assumes that the second element will contain the data() entry.
It will try to get the second record from the vtable, but the real vtable (created in mod2.h ) has only one record! Therefore, it tries to access invalid memory, and everything fails.
In short, consider the definition of these two structures in two different header files in C:
No one expected this to work. The program will often access the wrong memory. Therefore, you should not mess with vtables.