Virtual Method Table (vtable)
CS 301 Lecture, Dr. Lawlor
What are virtual methods?
A method in C++ is normally determined by the type of the class, as listed in the calling code.
If you put "virtual"
in front of the method declaration, the compiler will figure out at
runtime exactly which method should be called. This is generally
used to build generic interfaces everybody can call, which you then
implement with lots of different specific implementations. For
example, here we've got a generic "outputter" object that is used by
vec3, with several different implementations:
/** General outputter interface object (should use "pure virtual" methods) */
class outputter {
public:
virtual void pint(int i) {std::cout<<"I have no idea how to print an int\n";}
virtual void pstr(const char *s) {std::cout<<"No string print here.\n";}
};
/** Specific outputter that writes data onscreen */
class output_screen : public outputter {
public:
virtual void pint(int i) {std::cout<<i;}
virtual void pstr(const char *s) {std::cout<<s;}
};
/** Specific outputter that writes binary data */
class output_binary : public outputter {
public:
virtual void pint(int i) {std::cout.write((char *)&i,sizeof(i));}
virtual void pstr(const char *s) {std::cout.write((const char *)s,strlen(s));}
};
/** Any class that needs to print things can now use a generic "outputter" */
class vec3 {
public:
int x,y,z;
void print(outputter *p) {
p->pint(x); p->pint(y); p->pint(z); p->pstr("\n");
}
};
int foo(void) {
vec3 v; v.x=1; v.y=2; v.z=3;
output_screen os; v.print(&os); /* write vector to the screen */
return 0;
}
(Try this in NetRun now!)
How are classes stored in memory?
Let's poke around a bit inside a class. For example, this class
has one data member, a long, so it should be a total of 8 bytes:
/* Print an array of longs (or pointers) */
void larray_print(long *data,unsigned int nbytes,const char *desc) {
printf("%s at %p\n",desc,data);
for (unsigned int i=0;i<nbytes/sizeof(long);i++)
printf(" %s[%d]=%p\n",desc,i,(void *)data[i]);
}
/* Class to analyze */
class I_got_data {
public:
long A;
I_got_data() {A=0xAAAAaaa1AAAAaaa0;}
};
int foo(void) {
I_got_data v;
long *lv=(long *)&v;
larray_print(lv,sizeof(v),"class instance");
return sizeof(v);
}
(Try this in NetRun now!)
Indeed, the class still takes 8 bytes even if we add methods to the class.
But if we make the class's methods virtual (so they can be overridden
by child classes), then suddenly the class takes 16 bytes. We can
poke around in memory to find what's using those extra 8 bytes:
/* Class to analyze */
class I_got_methods {
public:
long A;
I_got_methods() {A=0xAAAAaaa1AAAAaaa0;}
virtual void bar0(void) {std::cout<<"That's the bar0 method!\n";}
virtual void bar1(void) {std::cout<<"That's the bar1 method!\n";}
};
int foo(void) {
I_got_methods v;
long *lv=(long *)&v;
larray_print(lv,sizeof(v),"class instance");
long *vt=(long *)*lv; /* points to class's vtable */
larray_print(vt,16,"class's vtable");
typedef void (*fnptr)(void);
fnptr f=(fnptr)vt[0]; /* first entry in vtable is our first virtual method */
f(); /* call that method */
return sizeof(v);
}
(Try this in NetRun now!)
This prints out:
class instance at 0x7fffffffe650 <- on the stack.
class instance[0]=0x401ef0 <- that's the vtable pointer
class instance[1]=0xaaaaaaa1aaaaaaa0 <- that's "A", a data member
class's vtable at 0x401ef0 <- look at vtable more closely
class's vtable[0]=0x400eb4 <- that's bar0
class's vtable[1]=0x400ed2 <- that's bar1
That's the bar0 method! <- yes, it is!
Program complete. Return 16 (0x10)
Each instance of a class with virtual methods contains a hidden pointer
to the compiler-generated virtual method table (vtable), that is used
to find the actual methods to call at runtime.