Low level details of inheritance and polymorphism
This question is one of the big doubts that looms around my head and is also hard to describe it in terms of words . Some times it seems obvious and sometimes a tough one to crack.So the question goes like this::
class Base{ public: int a_number; Base(){} virtual void function1() {} virtual void function2() {} void function3开发者_StackOverflow中文版() {} }; class Derived:public Base{ public: Derived():Base() {} void function1() {cout << "Derived from Base" << endl; virtual void function4() {cout << "Only in derived" << endl;} }; int main(){ Derived *der_ptr = new Derived(); Base *b_ptr = der_ptr; // As just address is being passed , b_ptr points to derived object b_ptr -> function4(); // Will Give Compilation ERROR!! b_ptr -> function1(); // Calls the Derived class overridden method return 0; }
Q1. Though b_ptr is pointing to Derived object, to which VTABLE it accesses and HOW ? as b_ptr -> function4() gives compilation error. Or is it that b_ptr can access only upto that size of Base class VTABLE in Derived VTABLE?
Q2. Since the memory layout of the Derived must be (Base,Derived) , is the VTABLE of the Base class also included in the memory layout of the Derived class?
Q3. Since the function1 and function2 of base class Vtable points to the Base class implementation and function2 of Derived class points to function2 of Base class, Is there really a need of VTABLE in the Base class?? (This might be the dumbest question I can ever ask, but still I am in doubt about this in my present state and the answer must be related to answer of Q1 :) )
Please Comment.
Thanks for the patience.
As a further illustration, here is a C version of your C++ program, showing vtables and all.
#include <stdlib.h>
#include <stdio.h>
typedef struct Base Base;
struct Base_vtable_layout{
void (*function1)(Base*);
void (*function2)(Base*);
};
struct Base{
struct Base_vtable_layout* vtable_ptr;
int a_number;
};
void Base_function1(Base* this){}
void Base_function2(Base* this){}
void Base_function3(Base* this){}
struct Base_vtable_layout Base_vtable = {
&Base_function1,
&Base_function2
};
void Base_Base(Base* this){
this->vtable_ptr = &Base_vtable;
};
Base* new_Base(){
Base *res = (Base*)malloc(sizeof(Base));
Base_Base(res);
return res;
}
typedef struct Derived Derived;
struct Derived_vtable_layout{
struct Base_vtable_layout base;
void (*function4)(Derived*);
};
struct Derived{
struct Base base;
};
void Derived_function1(Base* _this){
Derived *this = (Derived*)_this;
printf("Derived from Base\n");
}
void Derived_function4(Derived* this){
printf("Only in derived\n");
}
struct Derived_vtable_layout Derived_vtable =
{
{ &Derived_function1,
&Base_function2},
&Derived_function4
};
void Derived_Derived(Derived* this)
{
Base_Base((Base*)this);
this->base.vtable_ptr = (struct Base_vtable_layout*)&Derived_vtable;
}
Derived* new_Derived(){
Derived *res = (Derived*)malloc(sizeof(Derived));
Derived_Derived(res);
return res;
}
int main(){
Derived *der_ptr = new_Derived();
Base *b_ptr = &der_ptr->base;
/* b_ptr->vtable_ptr->function4(b_ptr); Will Give Compilation ERROR!! */
b_ptr->vtable_ptr->function1(b_ptr);
return 0;
}
Q1 - name resolution is static. Since b_ptr is of type Base*, the compiler can't see any of the names unique to Derived in order to access their entries in the v_table.
Q2 - Maybe, maybe not. You have to remember that the vtable itself is simply a very common method of implementing runtime polymorphism and is actually not part of the standard anywhere. No definitive statement can be made about where it resides. The vtable could actually be some static table somewhere in the program that is pointed to from within the object description of instances.
Q3 - If there's a virtual entry in one place there must be in all places otherwise a bunch of difficult/impossible checks would be necessary to provide override capability. If the compiler KNOWS that you have a Base and are calling an overridden function though, it is not required to access the vtable but could simply use the function directly; it can even inline it if it wants.
A1. The vtable pointer is pointing to a Derived vtable, but the compiler doesn't know that. You told it to treat it as a Base pointer, so it can only call methods that are valid for the Base class, no matter what the pointer points to.
A2. The vtable layout is not specified by the standard, it isn't even officially part of the class. It's just the 99.99% most common implementation method. The vtable isn't part of the object layout, but there's a pointer to the vtable that's a hidden member of the object. It will always be in the same relative location in the object so that the compiler can always generate code to access it, no matter which class pointer it has. Things get more complicated with multiple inheritance, but lets not go there yet.
A3. Vtables exist once per class, not once per object. The compiler needs to generate one even if it never gets used, because it doesn't know that ahead of time.
b_ptr points to the Derived vtable- but the compiler can't guarantee that the derived class has a function_4 in it, as that's not contained within the base vtable, so the compiler doesn't know how to make the call and throws an error.
No, the vtable is a static constant somewhere else in the program. The base class merely holds a pointer to it. A Derived class may hold two vtable pointers, but it might not.
In the context of these two classes, then Base needs a vtable to find Derived's function1, which actually is virtual even though you didn't mark it as so, because it overrode a base class virtual function. However, even if this wasn't the case, I'm pretty sure that the compiler is required to produce the vtables anyway, as it has no idea what other classes you have in other translation units that may or may not inherit from these classes and override their virtual functions in unknowable ways.
First, and most important, remember that C++ doesn't do a lot of run-time introspection of any kind. Basically, it needs to know everything about the objects at compile time.
Q1 - b_ptr is a pointer to a Base. Therefore it can only access things that are present in a Base object. No exceptions. Now, the actual implementation may change depending on the actual type of the object, but there's no getting around the method having to be defined in Base if you want to call it through a Base pointer.
Q2 - The simple answer is 'yes, the vtable for a base has to be present in a derived', but there are a LOT of possible strategies for how to layout a vtable, so don't get hung up on it's exact structure.
Q3 - Yes, there must be a vtable in the Base class. Everything that calls virtual functions in a class will go through the vtable so that if the underlying object is actually a Derived, everything can work.
Now that's not an absolute, because if the compiler can be ABSOLUTELY sure that it knows what it's got (as might be the case of a Base object that's declared on the local stack), then the compiler is allowed to optimize out the vtable lookups, and might even be allowed to inline the function.
All of this depends on the implementation. But here are the answers for the usual simplest way using "vtables".
The Base
class has a vtable pointer, so the underlying representation is something like this pseudo-code:
struct Base {
void** __vtable;
int a_number;
};
void* __Base_vtable[] = { &Base::function1, &Base::function2 };
void __Base_ctor( struct Base* this_ptr ) { this_ptr->__vtable = __Base_vtable; }
The Derived
class includes a Base
class subobject. Since that has a place for a vtable, Derived
doesn't need to add another one.
struct Derived {
struct Base __base;
};
void* __Derived_vtable[] =
{ &Derived::function1, &Base::function2, &Derived::function4 };
void __Derived_ctor( struct Derived* this_ptr ) {
__Base_ctor( &this_ptr->__base );
this_ptr->__base.__vtable = __Derived_vtable;
}
The "vtable for the Base class", __Base_vtable
in my pseudocode, is needed in case somebody tries new Base();
or Base obj;
.
All of the above gets more complicated when multiple inheritance or virtual inheritance is involved....
For the line b_ptr -> function4();
, this is a compile-time error, not much related to vtables. When you cast to a Base*
pointer, you may only use that pointer in the ways defined by class Base
(because the compiler doesn't "know" any more whether it's really a Derived
, a Base
, or some other class). If Derived
has a data member of its own, you can't access it through that pointer. If Derived
has a member function of its own, virtual or not, you can't access it through that pointer.
精彩评论