0

I found some excellent threads on this site for the subject, and the topic of polymorphism is cleared up for me, but I'm just confused how exactly a virtual function works versus a normal function.

(an example given in this thread Why do we need virtual functions in C++?):

class Animal
{
public:
    void eat() { std::cout << "I'm eating generic food."<<endl; }
};

class Cat : public Animal
{
public:
    void eat() { std::cout << "I'm eating a rat."<<endl; }
};

void func(Animal *xyz) { xyz->eat(); }

So We have a function and a derived function that was redefined.

Cat *cat = new Cat;
Animal *animal = new Animal;


animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."
func(animal);  // Outputs: "I'm eating generic food."
func(cat);     // Outputs: "I'm eating generic food."

So we can't access the function we want without it being a virtual function. But why, exactly?

If the following works:

Cat cat;
Animal animal;

animal.eat(); // Outputs: "I'm eating generic food."
cat.eat();    // Outputs: "I'm eating a rat."

Then presumably there are two different eat functions in memory already without needing a vtable.

So when we make eat a virtual function, each class now gets its own vTable with its own functions. So...We are just storing the functions in another place in memory. So what happens to a pointer between when it calls a regular function through an object and when it calls a virtual function through an object?

As in what's the difference between: Animal->eat(); //Calling a virtual function and Animal->eat(); //Calling a regular function

When we declare virtual function, TutorialsPoint says

This time, the compiler looks at the contents of the pointer instead of it's type

Yes, but how? Why couldn't it do that before? It's presumably just stored in memory the same as a regular function. Does it have something to do with the Vtable pointer at the beginning of an object?

I'm just looking for specifics to understand why. I don't mean to sound like I'm getting bogged down in something ultimately pointless. Just wondering for academic reasons.

Akimbo
  • 63
  • 4
  • 2
    I don't really understand the question. For a regular function call the compiler knows at compile time what function to call. In the case of a virtual function, the lookup for what function to call is done at run time by dereferencing the pointer in the vtable. What exactly is it you are asking about? – super Mar 11 '19 at 00:46
  • This is really too broad. OP should see Stanley Lippmann, *Inside the C++ Object Model,* Addison Wesley. – user207421 Mar 11 '19 at 01:25

2 Answers2

2

Consider this code:

void Function(Animal *foo)
{
    foo->eat();
}

If eat is a non-virtual member function, this just calls Animal::eat. It makes no difference what foo points to.

If eat is a virtual member function, this is roughly equal to *(foo->eatPtr)();. You can think of Animal, and all its derived classes, as having a member variable that points to the eat function. So if foo actually points to a Bear, then foo->eatPtr() will access Bear::eatPtr and call the eat function of the Bear class.

Which function to call is determined at compile time for non-virtual functions. So this will always call the same eat function. For a virtual function, the pointer passed in is used to find the appropriate virtual function table for the particular class that foo happens to be a member of.

This extra class member variable that points to the vtable for the class is why the size of either a class instance or its pointers (depending on the implementation) will typically go up by the size of one pointer when you add the first virtual function to that class.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • I know you said to just think of it this way, but obviously there is no member variable in a class pointing to a regular function. I assume when a pointer to a class is dereferenced to call a function, that class is passed a this pointer. But what exactly happens when it's a virtual function? The pointer accesses the virtual table or the compiler just knows to access it differently? – Akimbo Mar 11 '19 at 00:25
  • The usual way to do it is to pass a pointer to a class instance that has at least one virtual function by actually passing two pointers, one to the class instance and one the virtual function table for the class. The compiler always knows whether a class has zero or more than zero virtual functions. My example code can only call a `virtual` function if there is at least one `virtual` function in class `Animal` or any of its parents. On most implementations, `sizeof(Animal*)` will depend on whether or no this is the case. – David Schwartz Mar 11 '19 at 00:43
  • Usually each instance contains its own VFT pointer, and those of its parent classes if any. See Stanley Lippmann, *Inside the C++ Object Model*. – user207421 Mar 11 '19 at 00:54
  • The compiler can't pass the VFT pointer as an extra parameter, because it doesn't know the actual type of the object. If it did, we wouodn't need vtables at all. – user207421 Mar 11 '19 at 01:55
  • @David Schwartz those are the specifics I was looking for. Though it looks like there may be disagreements below you. – Akimbo Mar 11 '19 at 03:18
  • @user207421 It was my understanding from reversing my test programs that a derived class gets its own VFT, that may point to some of the same functions along with different virtual functions, but is a separate VFT entirely from what it inherits. And to your second post, those are the specifics I'm trying to figure out. – Akimbo Mar 11 '19 at 03:18
  • @user207421 The compiler doesn't need to know the type of the object, it just needs the VFT pointer. If you always pass instances of a class as a pair -- a pointer to the first member variable and a pointer to the VFT, that works just fine. An alternate solution is to include the VFT pointer as a member variable, but there are complex reasons why that's a worse solution. – David Schwartz Mar 11 '19 at 04:50
  • The VFT pointer essentially *is* the type of the object, and surely the only way the compiler can get it is via a VFT pointer embedded *in* the object. How can it just manufacture a VFT parameter when calling a method on an object pointer of unknown actual type? – user207421 Mar 11 '19 at 05:21
  • @user207421 The same way it manufactures the pointer to the object itself -- it just always keeps those two things together. In fact, you'll find that `sizeof(Foo*)` will equal `2 * sizeof(Bar*)` if `Foo` has a VFT and `Bar` doesn't because a `Foo*` includes both a pointer to the data and a pointer to the VTF for Foo. – David Schwartz Mar 12 '19 at 04:19
0

I find it best to implement it to understand it.

struct ifoo;
struct ifoo_vtable{
  void(*print)(ifoo const*);
};

struct ifoo{
  ifoo_vtable const* vtable;
  void print()const{ vtable->print(this); }
};

struct fooa:ifoo{
  void print_impl()const{ std::cout<<"fooa\n"; }
  fooa(){
    static const ifoo_vtable mytable={
      +[](ifoo const* self){
        static_cast<fooa const*>(self)->print_impl();
      }
    };
    vtable=&mytable;
  }
};
struct foob:ifoo{
  void print_impl()const{ std::cout<<"foob\n"; }
  foob(){
    static const ifoo_vtable mytable={
      +[](ifoo const* self){
        static_cast<foob const*>(self)->print_impl();
      }
    };
    vtable=&mytable;
  }
};

now there is zero use of virtual above. But:

fooa a;
foob b;
ifoo* ptr = (rand()%2)?&a;&b;
ptr->print();

will randomly call either fooa or foob's print_impl method.

The vtable is a structure of pointers to functions. Invoking a virtual methid actuall runs a little stub that looks up the method in the vtable, then runs it.

Code written for you in implementatiin classes' constructors fills that vtable with function pointers pointing at the overrides.

Now there are details that are not done here -- calling conventions, destructors, devirtualization, multiple interface inheritance, dynamic casts, etc -- but the core is pretty similar to how every major C++ compiler implements virtual methods.

Under the standard this is not detailed; only behaviour is. But this kind of vtable comes from before C++ was a language, and this kind of vtable is what I believe the C++ language designers had in mind when they specified the behaviour of virtual functions in C++.

Note that this is far from the only way to do this. MFC message maps, objective C/small talk based messages, python, lua, and many other languages have other ways to do this with advantages and disadvantages over C++'s solution.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524