27

I was looking for some information about virtual tables, but I can't find anything that is easy to understand.

Can somebody give me good examples with explanations?

cigien
  • 57,834
  • 11
  • 73
  • 112
lego69
  • 767
  • 1
  • 12
  • 20

5 Answers5

26

Without virtual tables you wouldn't be able to make runtime polymorphism work since all references to functions would be bound at compile time. A simple example

struct Base {
  virtual void f() { }
};

struct Derived : public Base {
  virtual void f() { }
};

void callF( Base *o ) {
  o->f();
}

int main() {
  Derived d;
  callF( &d );
}

Inside the function callF, you only know that o points to a Base object. However, at runtime, the code should call Derived::f (since Base::f is virtual). At compile time, the compiler can't know which code is going to be executed by the o->f() call since it doesn't know what o points to.

Hence, you need something called a "virtual table" which is basically a table of function pointers. Each object that has virtual functions has a "v-table pointer" that points to the virtual table for objects of its type.

The code in the callF function above then only needs to look up the entry for Base::f in the virtual table (which it finds based on the v-table pointer in the object), and then it calls the function that table entry points to. That might be Base::f but it is also possible that it points to something else - Derived::f, for instance.

This means that due to the virtual table, you're able to have polymorphism at runtime because the actual function being called is determined at runtime by looking up a function pointer in the virtual table and then calling the function via that pointer - instead of calling the function directly (as is the case for non-virtual functions).

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
  • 4
    Maybe it's worth mentioning how the lookup of `Base::f` in the v-table works: the pointer to the function `f()` is always stored at the same offset for all type-compatible classes (according to [Wikipedia](https://en.wikipedia.org/wiki/Virtual_method_table) ). E.g., if in `Base`'s v-table the pointer to `Base::f()` is stored at offset `+4` then in `Derived`'s v-table the pointer to `Derived::f()` is also stored at offset `+4`. – kafman Dec 21 '18 at 14:02
13

The virtual function table is an implementation detail - it's how the compiler implements polymorphic methods in classes.

Consider

class Animal
{
   virtual void talk()=0;
}

class Dog : Animal
{
   virtual void talk() {
       cout << "Woof!";
   }
}

class Cat : Animal
{
   virtual void talk() {
       cout << "Meow!";
   }
}

And now we have

   A* animal = loadFromFile("somefile.txt"); // from somewhere
   animal->talk();

How do we know which version of talk() is called? The animal object has a table that points to the virtual functions that are used with that animal. For example, talk may be at the 3rd offset, if there are two other virtual methods:

   dog
   [function ptr for some method 1]
   [function ptr for some method 2]
   [function ptr for talk -> Dog::Talk]

   cat
   [function ptr for some method 1]
   [function ptr for some method 2]
   [function ptr for talk -> Cat::Talk]

When we have an instance of Animnal, we don't know which talk() method to call. We find it by looking in the virtual table and fetching the third entry, since the compiler knows that corresponds to the talk pointer (the compiler knows the virtual methods on Animal, and so knows the order of pointers in the vtable.)

Given an Animal, to call the right talk() method, the compiler adds code to fetch the 3rd function pointer and use that. This then directs to the appropriate implementation.

With non-virtual methods, this is not necessary since the actual function being called can be determined at compile time - there is only one possible function that can be called for a non-virtual call.

mdma
  • 56,943
  • 12
  • 94
  • 128
  • A* animal = ....; // from somewhere animal->talk(); in this line can You be more specific, thanks – lego69 Jun 09 '10 at 09:47
  • 1
    Sure - imagine the animal is loaded from file, or a different animal is created depending upon user input. The intent is that the compiler has no way of knowing what type of animal we have. – mdma Jun 09 '10 at 10:05
5

To answer your headline question - you don't, and the C++ Standard does not specify that you must be provided with one. What you do want is to be able to say:

struct A {
  virtual ~A() {}
  virtual void f() {}
};

struct B : public A {
  void f() {}
};

A * p = new B;
p->f();

and have B::f called and not A::f. A virtual function table is one way of implementing this, but is frankly not of interest to the average C++ programmer - I only ever think about it when answering questions like this.

  • For an example of alternative, python stores the methods along with the attributes right in the object. It thus accomplishes this behavior without using a `virtual` table, though it's very similar. – Matthieu M. Jun 09 '10 at 09:46
  • @Matthieu M, python does exactly the same thing as c++ - it stores some reference to a method object (in c-python implemented with pointers), while c++ stores the adress of the method. The difference is mainly that the python tables are stored per-object and not per-class, since python allows attributes and methods to be added at runtime. – gnud Aug 24 '10 at 09:09
  • 1
    I actually disagree: in C++ the virtual table has no actual information on the pointers to function stored, the compiler just knows the required method is at a given index. On the other hand in python the attributes and methods are (generally) stored in a dictionnary and you do your look up by name. It's a major difference in implementation, which gives python more flexibility at the cost of performance. – Matthieu M. Aug 24 '10 at 16:29
2

Suppose Player and Monster inherit from an abstract base class Actor that defines a virtual name() operation. Further suppose that you have a function that asks an actor for his name:

void print_information(const Actor& actor)
{
    std::cout << "the actor is called " << actor.name() << std::endl;
}

It is impossible to deduce at compile time whether the actor will actually be a player or a monster. Since they have different name() methods, the decision which method to call must be deferred until runtime. The compiler adds additional information to each actor object that allows this decision to be made at runtime.

In every compiler I know, this additional information is a pointer (often called vptr) to a table of function pointers (often called vtbl) that are specific to the concrete class. That is, all player objects share the same virtual table which contains pointers to all player methods (same goes for the monsters). At runtime, the correct method is found by choosing the method from the vtbl pointed to by the vptr of the object on which the method should be invoked.

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
1

Short answer: virtual function call, basePointer->f(), means different things depending on the history of basePointer. If it points to something that Really is a derived class, a different function will be called.

For this, compiler does a simple game of function pointers. Addresses of functions to be called for different types are stored in virtual table.

Virtual table is not used only for function pointers. The RTTI machinery uses it for run-time type information (obtaining actual types of an object referenced by an address of one of the base types).

Some new/delete implementations would store the object size in the virtual table.

Windows COM programming uses virtual table to crack into it and push it as an interface.

Pavel Radzivilovsky
  • 18,794
  • 5
  • 57
  • 67