In this video, I'd like to show the Virtual Method Table approach that is used to implement virtual functions in C++. This method is used by most common C++ compilers like MSVC, Clang, or GCC.
Excellent explanation. The most essential part that I was missing was that the compiler secretly inserts a pointer at the beginning of every instance of a virtual class, pointing to that virtual class its vtable, so that a copy of the vtable doesn't have to be stored in the instance.
My question is, why doesn't the compiler just insert the correct method address at object construction instead of going through this whole vtable business? When I look at the assembly that gcc generates for example, when an object of the given class is constructed, it loads whatever data members there are for the instance onto the stack, and then loads the *address of the vtable record for the function*. WHY can't it just, instead of loading the vtable address+offset, load the function address here, itself? What do we gain from using the vtable? I thought at first it was explained by easing compilation, but really it's no more of a burden on compilation to use the method address directly here, since the linker will still just resolve the symbol in any calling code that constructs one of these objects. I must be missing something.
That's a good question, let me try to answer. In some cases it is true that the compiler can figure out the final address of the function. That is true especially when all necessary information is available in the scope. You can try to put both parent and child classes in the same cpp file and turn optimization on (I'm not familiar with GCC but you can try -O3). The function call should be inlined in assembly, at least that's what MSVC does: pastebin.com/cUHQXuxX (the object is created as usual because memory operations can't be optimized away) However, there are cases when we need or it is easier for the compiler to use dynamic dispatch described in the video. One such case is when you allow a user to pick concrete type of the object that's going to be created like this example: pastebin.com/ZhyLgRVx There's no way for a compiler to be sure which address to get at compile time and this is when vtable comes into play, or more formally, dynamic dispatch. Another case is when the function we're trying to invoke resides in different scope/translation unit or the compiler decides it is not worth to inline the function: pastebin.com/YpQh7TaC The function can't be sure what type of the polymorphic object it has (the same function can be used many times with different objects that share the same inheritance chain) so it decides to use vtable which will work for all of them at the cost of additional instructions. Hopefully that makes it a bit more clear. PS. I know there's memory leak in the examples but to make it easier to read I tried to skip "unnecessary" stuff.
Two missing things I can think of: a) would be nice to cover diamond problem as well (a.k.a. virtual inheritance: en.wikipedia.org/wiki/Virtual_inheritance) b) fun things can happen with vtbl (and there's one particular compiler that was doing this), when you combine inheritance with overloading (i.e. `virtual void handler(your_fave_ptr_type)` + `virtual const Handler& handler()` - perhaps not the best example, but you get the idea)