Max Lybbert
Posts: 314
Nickname: mlybbert
Registered: Apr, 2005
|
|
Re: How C++ programmers think
|
Posted: Dec 30, 2005 11:11 AM
|
|
/* OK, now I am confused. This is very different from a vtable implementation, is it not? */
To be honest, I only really looked into this a little while ago. Let me try to write some pseudocode, and perhaps somebody with more knowledge can correct any mistakes.
Hardware simply doesn't have object-oriented features, generally speaking (http://slashdot.org/developers/00/02/25/1034222.shtml question 8). So OOP languages have to use what the hardware does offer in order to implement OOP. Instead of doing that, I'm going to try to implement OOP in C.
A non-virtual method in C++ maps to a function in C. So, if I have a class (say, dog) that has a method (say, bark), C++ is going to implement that with a data element (we'll call that [object]_data), and a function that works on that data.
So:
dog Fido("dalmation"); Fido.bark(3);
Turns into something like:
struct dog_data {char* species, int age_in_years}; struct dog_data Fido_data = dog_ctor("dalmation", 3); dog_bark(Fido_data, 3);
The type system remembers that Fido is a dog. However, if I try to refer to Fido as an animal instead of a dog, the type system is going to grap the animal_ version of the functions, and is only going to look at the animal_data portion of Fido_data (this is called slicing). If I did not extend those methods or data, then I won't notice a problem. But if I made a change to one or the other, I'll probably get wrong behavior.
A vtable is a table of function pointers that goes in the object's data portion, and those pointers refer to the functions that implement all virtual methods related to that object.
So if dog were derived from animal, and bark were renamed make_noise, and virtual, there would be a vtable somewhere in animal_data, and one of those pointers would refer to the make_noise function of the derived class.
The magic comes from the computer knowing where this table is, and what kind of function each entry points to. By deriving dog from animal, the compiler puts a pointer to dog_make_noise at the right spot in dog_data, so that I can guarantee the correct function gets called on the correct data, even if I'm using a base class instead of the derived class:
struct animal_data {void* animal_vtable[10]}; struct dog_data {animal_data, string species, int age_in_years};
If animal_vtable[0] is a pointer to the correct make_noise function, dog_data.animal_data.animal_vtable[0] will point at dog_make_noise. And since we're talking about single inheritance and C-style structs, that entry will be both dog_data's and animal_data's very first byte. So I can lookt at the first byte data of any class derived from animal_data to get the pointer for the correct make_noise.
The compiler takes care of the details. I'm not going to write the code for actually doing that, because pointer to function syntax is one thing I have to double check everytime I think about using it. In fact, I'm pretty sure void* animal_vtable[10] isn't the correct way to make a table of function pointers.
Multiple inheritance, btw, is implemented by putting a table of pointers to the various vtables at the beginning of the object's data component. So if class dog derives from both animal and graphic (so it can be displayed), there will be a table at the beginning of dog_data, with one pointer to where animal_vtable is and another to where graphic_vtable is within the dog_data structure.
|
|