Would someone please explain to me the bold parts?
I didn't get how come *__vptr which is in the Base portion of the class and dPtr has access to this pointer CAN all of a sudden point to the D1 virtual table instead of the Base virtual table! I have read some articles, watched some sources and still confused.
class Base
{
public:
virtual void function1() {};
virtual void function2() {};
};
class D1: public Base
{
public:
virtual void function1() {};
};
class D2: public Base
{
public:
virtual void function2() {};
};
int main()
{
D1 d1;
Base *dPtr = &d1;
return 0;
}
Note that because dPtr is a base pointer, it only points to the Base portion of d1. However, also note that *__vptr is in the Base portion of the class, so dPtr has access to this pointer. Finally, note that dPtr->__vptr points to the D1 virtual table! Consequently, even though dPtr is of type Base, it still has access to D1’s virtual table (through __vptr).
Source: https://www.learncpp.com/cpp-tutorial/125-the-virtual-table/comment-page-6/#comment-484189
To understand c++ you need to understand that the implementation of C++ is not the definition of C++.
C++ is defined by the behavior of an abstract machine. This abstract machine's behavior is defined by the standard, and a compliant C++ compiler must compile programs to run as-if they ran on that abstract machine.
The rules of what that abstract machine does are inspired and based off of real computers and real implementations of C++ and C programs.
So when you talk about "virtual function tables", you are talking about one common implementation of what C++ does with virtual methods. That common implementation doesn't define how C++ acts, and mixing the two up can cause problems.
That being said, C++'s virtual methods where based off of doing basically the exact same thing in C. It can help to sketch out how virtual methods and inheritance would work in C++ if you re-implemented it. (this has practical use, because doing so lets you make a custom object model, and custom object models let you do certain things more efficiently that the C++ object model would).
here is a really simple, no inheritance, implementation of a class
Bob
with a single virtual methodprint
. It roughly corresponds to:you can see why it is nice to have all of this glue code written for you.
When you do this:
the "manual implementation" would look someting like:
and there you have it.
Take a look at what happens here:
now,
a.print
actually callsBob::print
, becauseAlice
has noprint
method.Bob.print
does this:it grabs the vtable pointer of this object instance, and calls the print function in it.
What is the vtable pointer of an object of type
Alice
? Look at theAlice
constructor.First it default-constructs
Bob
(which setsvtable
to point atBob
's vtable), but then it does this:this call to
get_vtable
callsAlice::get_vtable
:which in turn calls
Alice::make_vtable
:which first calls
Bob
'smake_vtable
, then replaces.print
with theAlice::print_impl
.So
Bob::print
callsvtable->print(this)
, which isAlice::print_impl(this)
, which does:While
this
is aBob const*
at this point, it is pointing at anAlice
object, so thatstatic_cast
is valid.So we print
x
andy
fromAlice
.Now, here
Alice
's vtable type isBob_vtable
because she didn't add any new methods. If she added new methods, she would have anAlice_vtable
that inherited fromBob_vtable
, and would have tostatic_cast<Alice_vtable const*>(vtable)
to access them.This isn't quite exactly what c++ does "under the hood", but it is about as logically identical as I can write "off the cuff". There are a myriad of different details, like the calling convention of the functions in the vtable is different, and the format of the vtable in memory doesn't match that, etc.
Now, in the 'manual implementation' I did use inheritance. So that isn't C; but the inheritance in the 'manual implementation' is not doing anything object oriented.
is just doing
with a bit of syntactic glitter on top.
The "manual implementation" is nearly 1:1 on how you would implement this (and people do) in c. You would move the methods out of the class, call them
void Bob_print(Bob const*)
instead ofvoid Bob::print() const
. And you'd usestruct Alice { Bob base; int y; }
instead ofstruct Alice:Bob{ int y; };
. But the difference is almost completely syntax, not anything else.When c++ was originally developed, OO-based C existed, and one of C++'s first goals was to be able to write C-with-classes without having to write all of the above boilerplate.
Now, C++'s object model does not require the above implementation. In fact, relying on the above implementation can result in ill-formed programs or undefined behavior. But understanding one possible way to implement C++'s object model has some use; plus, once you know how to implement C++'s object model, you can use different object models in C++.
Note that in modern C++, I'd use a lot more templates above to remove some of the boilerplate. As a practical use, I've used similar techniques to implement augmented
std::any
's with duck-typed virtual methods.The result is you can get this syntax:
(don't try this at home).