开发者

Why do we need a virtual table?

开发者 https://www.devze.com 2023-01-02 15:49 出处:网络
I was looking for some information about virtual tables, but I can\'t find anything that is easy to understand.

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 exampl开发者_开发技巧es with explanations?


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).


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.


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.


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消