Thread: Vtables and Virtuals

  1. #1
    The Dragon Reborn
    Join Date
    Nov 2009
    Location
    Dublin, Ireland
    Posts
    629

    Vtables and Virtuals

    grr...seriously, it feels like I am going in circles

    I just don't see the point of polymorphism...i mean I see the the point point, but not how it is implemented.
    Why we need it to be pointers, and then the use of virtuals..

    When a child class inherits from a base class, assuming they had the same function print() for example - print() is not virtual. the print() for the derived class(assuming an implementation was made for it) would execute, otherwise the base class's execute..
    If it is able to do that already, why do we need to use virtual keyword(apart from making a class abstract) at all?
    if there is no use of virtuals, then no need for vtables..

    as for vtables, the compiler makes a table of virtual (non pure) functions in the order of which they're declared i assume, the functions belong to their respective class(how they're implemented differently)
    which in turn points to a function itself..at least what the book said...again I don't see the point of vtables..
    if i was to
    childClass *ptr;

    ptr->print()...it is going to know anyway which print() function to execute, automatically overriding a the base class's if a body is provided for the child class..if it can do that anyway, why have a vtable vector in memory????!

    While I see why the concept of polymorphism is useful, I just don't get how it works "under the hood"..can someone please enlighten me.

    Thanks much.
    Last edited by Eman; 01-31-2011 at 02:27 PM.
    You ended that sentence with a preposition...Bastard!

  2. #2
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    The point of polymorphism, roughly:
    Code:
    class Fruit;
    
    class Apple : public Fruit;
    
    class Banana : public Fruit;
    
    class Grape : public Fruit;
    Now I make a big array of Fruit, some of which are Apples, some of which are Banana, some of which are Grape.
    Code:
    std::vector <Fruit *> working_vec;
    std::vector <Fruit> non_working_vec;
    If I use pointers, then I can follow that pointer to the actual object and get that type information. If I don't use pointers, then I have just the Fruit information (since I can only put a Fruit in the array and not some other type), and not the specific information about Apples, Bananas, or Grapes. Therefore none of the override functions will be called, since now I just have a Fruit, and not an Apple (say).

  3. #3
    The Dragon Reborn
    Join Date
    Nov 2009
    Location
    Dublin, Ireland
    Posts
    629
    Quote Originally Posted by tabstop View Post
    The point of polymorphism, roughly:
    Code:
    class Fruit;
    
    class Apple : public Fruit;
    
    class Banana : public Fruit;
    
    class Grape : public Fruit;
    Now I make a big array of Fruit, some of which are Apples, some of which are Banana, some of which are Grape.
    Code:
    std::vector <Fruit *> working_vec;
    std::vector <Fruit> non_working_vec;
    If I use pointers, then I can follow that pointer to the actual object and get that type information. If I don't use pointers, then I have just the Fruit information (since I can only put a Fruit in the array and not some other type), and not the specific information about Apples, Bananas, or Grapes. Therefore none of the override functions will be called, since now I just have a Fruit, and not an Apple (say).
    Dang..i understood why we need pointers after posting this :S .. why does that happen? However, your explanation was clear
    I still don't understand the use of vtables though...
    You ended that sentence with a preposition...Bastard!

  4. #4
    The Dragon Reborn
    Join Date
    Nov 2009
    Location
    Dublin, Ireland
    Posts
    629
    To be clear..
    when the compiler compiles the code..it looks at a class
    if the class has a virtual function it creates a vtable for that class which in turn is a pointer to the function.

    My point, if there is one, is that there should be no reason for it to create a vtable.

    let's say I had a normal class
    Code:
    class A
    {
       int num ;
       public:
        void printNum() ;
    }
    class B:A
    {
      B():A()
      {
      }
     printNum()
      {
          cout << "I am B" ;
       }
    }
    when the compiler sees this code..it recognizes that if i did BObject.printNum() it would print B's implementation..
    So should it not make sense that if the compiler sees a function implementation of a base class
    and derived class it would create each of their function, and then all subsequent base or child objects can share their base or child printNum() function, respectively.
    this would get rid of the use of vtables...
    unfortunately I don't know if vtables are created per object..or per class if there is a virtual function present
    You ended that sentence with a preposition...Bastard!

  5. #5
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by Eman View Post
    ptr->print()...it is going to know anyway which print() function to execute, automatically overriding a the base class's if a body is provided for the child class..if it can do that anyway, why have a vtable vector in memory????!
    How is that going to work when the compiler doesn't know the type of object it is dealing with? Anywhere you can use a Derived pointer you can also use a Base pointer. If all you have is a Base pointer how will the compiler do the right thing and invoke the method of Derived? Because it has a vtable.

    If you observe cars driving on the road, and then you observe somebody putting gas into a car, do you ask "Why bother with the gas, the cars are driving already"
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  6. #6
    The Dragon Reborn
    Join Date
    Nov 2009
    Location
    Dublin, Ireland
    Posts
    629

    Unhappy

    Quote Originally Posted by brewbuck View Post
    How is that going to work when the compiler doesn't know the type of object it is dealing with? Anywhere you can use a Derived pointer you can also use a Base pointer. If all you have is a Base pointer how will the compiler do the right thing and invoke the method of Derived? Because it has a vtable.

    If you observe cars driving on the road, and then you observe somebody putting gas into a car, do you ask "Why bother with the gas, the cars are driving already"
    yeah...i'm still lost...
    the virtual keyword should be enough to make a class polymorphic...i am blind to see why the use of vtables is necessary...
    Code:
    class A
    {
       public:
          virtual print()
          {
               
                   cout << "Class A is the best"  << endl;
     
           }
    }
    class B
    {
            virtual print()
            {
                  cout <<"Nein! Class B is better!" << endl ;
             }
            
    }
    int main()
    {
       A *a[3];
       a[0] = new B() ;
       a[1] = new A() ;
       a[2] = new B() ;
       return 0;
    }


    from my POV when the compiler sees virtual and it sees that a both classes have an implementation for the print function...the compiler should designate, as if like a normal class (class with no virtual function),which function the object should call..
    I know it sounds a lot like static binding, but since the virtual keyword and that print has bodies in both classes..it should know depending on the object type which function to call...without vtable
    and similarly if i did it that class B doesn't have a body for print. then create the compiler should know that it would call class A print's version..
    no need for a vtable...again sounds like static binding..but polymorphism should still work..
    grrrr!
    Last edited by Eman; 01-31-2011 at 04:14 PM. Reason: for the laugh
    You ended that sentence with a preposition...Bastard!

  7. #7
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    I think you think that vtables are part of C++. They are not.

    You recognize that the compiler should designate which function (base or derived) should get called. vtables are how that happens (or at least one mechanism for such). It's not that vtables are somehow distinct from polymorphism. Polymorphism is this very abstract concept, and vtables are how that very abstract concept actually comes about in real life. (Sort of like the difference between "getting from point A to point B" and "walking".)

  8. #8
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Ok, here's a better example:

    Code:
    class Base
    {
    public:
        virtual void Print() { std::cout << "I am Base" << std::endl; }
    };
    
    class Derived : public Base
    {
    public:
        virtual void Print() { std::cout << "I am Derived" << std::endl; }
    };
    
    void InvokePrint(Base *object)
    {
        object->Print(); // How does it know which one?
    }
    
    int main()
    {
        Derived *d = new Derived;
        InvokePrint(d);
    }
    Within the InvokePrint() function, how does the call get dispatched correctly? The compiler just sees that the parameter is a Base *. Via what magic does it happen? The answer is the vtable.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  9. #9
    The Dragon Reborn
    Join Date
    Nov 2009
    Location
    Dublin, Ireland
    Posts
    629
    Quote Originally Posted by brewbuck View Post
    Ok, here's a better example:

    Code:
    class Base
    {
    public:
        virtual void Print() { std::cout << "I am Base" << std::endl; }
    };
    
    class Derived : public Base
    {
    public:
        virtual void Print() { std::cout << "I am Derived" << std::endl; }
    };
    
    void InvokePrint(Base *object)
    {
        object->Print(); // How does it know which one?
    }
    
    int main()
    {
        Derived *d = new Derived;
        InvokePrint(d);
    }
    Within the InvokePrint() function, how does the call get dispatched correctly? The compiler just sees that the parameter is a Base *. Via what magic does it happen? The answer is the vtable.
    vtable shouldn't necessarily be the answer...
    when it sees that object being pointed to by the pointer passed into InvokeFunction(Base *) is of type Derived, the compiler having compiled the code should have done it that the function that should be called should be of the Derived::Print() since a body was provided for it...

    I understand how the vtables works..but to be honest I don't think I will ever see the point of using them, when the compiler could have created a static binding for it on recognition of which function is virtual and if a function body was provided or not...and yet still work in a polymorphic manner.

    and has tabstop said...the vtables is not part of C++ ... so use of a virtual (is part of C++) should be enough to tell the compiler which function to execute..
    Last edited by Eman; 01-31-2011 at 05:05 PM.
    You ended that sentence with a preposition...Bastard!

  10. #10
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Quote Originally Posted by Eman View Post
    when it sees that object being pointed to by the pointer passed into InvokeFunction(Base *) is of type Derived,
    There is no way the compiler can even do this, a little bit. What if the function call happens in a different file? Different files aren't compiled at the same time. What if this code is in a library, so the source isn't even available? This has to happen at run-time, and can't be done at compile-time at all.0

  11. #11
    The Dragon Reborn
    Join Date
    Nov 2009
    Location
    Dublin, Ireland
    Posts
    629
    Quote Originally Posted by tabstop View Post
    There is no way the compiler can even do this, a little bit.
    Sorry to challenge you tabstop, but (and the book I read it from) that virtual keyword declares that the function call to be executed is based on the type of object being pointed to by the reference or pointer and not by the reference (or pointer).

    What if the function call happens in a different file? Different files aren't compiled at the same time. What if this code is in a library, so the source isn't even available? This has to happen at run-time, and can't be done at compile-time at all.
    even if the Classes are in separate files..when the compiler compiles each class...it should make little difference..
    but I don't know how a library works..
    and if there is no source available..then there is no program?? haha, no idea. But how does the compiler make an exe then lol.
    You ended that sentence with a preposition...Bastard!

  12. #12
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Quote Originally Posted by Eman View Post
    Sorry to challenge you tabstop, but (and the book I read it from) that virtual keyword declares that the function call to be executed is based on the type of object being pointed to by the reference or pointer and not by the reference (or pointer).
    Which is my point, so that's good. (All the information the compiler knows is the type of the reference/pointer. The compiler doesn't know the type of object being pointed to.)

    Say we have the code
    Code:
    void foo(Base *p)
    {
        p->invokePrint();
    }
    You as the compiler: that's all you get. Does p point at a Base object, or a Derived object? You have no way of knowing. And of course, it can change from one call of foo to the next. That's why you rely on a run-time check, and can't try to "hard-code" it into the final executable.

    Quote Originally Posted by Eman View Post
    even if the Classes are in separate files..when the compiler compiles each class...it should make little difference..
    but I don't know how a library works..
    and if there is no source available..then there is no program?? haha, no idea. But how does the compiler make an exe then lol.
    You've been using libraries (probably without source) all the time. Do you have the source for things like cout <<, or cin >>? All that matters is that the library has a hook: when you want to call function whatever, this is the section of code to run.

  13. #13
    The Dragon Reborn
    Join Date
    Nov 2009
    Location
    Dublin, Ireland
    Posts
    629
    Quote Originally Posted by tabstop View Post
    Which is my point, so that's good. (All the information the compiler knows is the type of the reference/pointer. The compiler doesn't know the type of object being pointed to.)

    Say we have the code
    Code:
    void foo(Base *p)
    {
        p->invokePrint();
    }
    You as the compiler: that's all you get. Does p point at a Base object, or a Derived object? You have no way of knowing. And of course, it can change from one call of foo to the next. That's why you rely on a run-time check, and can't try to "hard-code" it into the final executable.
    so basically this:
    Code:
       int num 
       int *ptr = &num
    is different from this

    Code:
     Base *ptr ;
     ptr = new Derived()
    for the first code..ptr knows that it is definitely pointing to an int object, and therefore the linker fixes up the address..
    for the second code..ptr doesn't know and so the linker doesn't fix up the address?
    You ended that sentence with a preposition...Bastard!

  14. #14
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Quote Originally Posted by Eman View Post
    so basically this:
    Code:
       int num 
       int *ptr = &num
    is different from this

    Code:
     Base *ptr ;
     ptr = new Derived()
    for the first code..ptr knows that it is definitely pointing to an int object, and therefore the linker fixes up the address..
    for the second code..ptr doesn't know and so the linker doesn't fix up the address?
    I don't have any idea what you mean about "fixes up the address". In both cases, you have a pointer that points somewhere. In the first case, int is a basic type, there's not anything amazing going on, and the compiler thinks that the object being pointed to is an int. In the second case, the Derived constructor constructs an object, the address of the object is put into ptr. As far as the compiler is concerned, that object is a Base, since it is being pointed to by a Base *. At run-time, the actual contents of that memory will be checked (when necessary) to see what actual kind of object is there.

  15. #15
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by tabstop View Post
    I don't have any idea what you mean about "fixes up the address". In both cases, you have a pointer that points somewhere. In the first case, int is a basic type, there's not anything amazing going on, and the compiler thinks that the object being pointed to is an int. In the second case, the Derived constructor constructs an object, the address of the object is put into ptr. As far as the compiler is concerned, that object is a Base, since it is being pointed to by a Base *. At run-time, the actual contents of that memory will be checked (when necessary) to see what actual kind of object is there.
    I think maybe the confusion is that he's thinking "Well, stupid compiler, can't it SEE that the thing is a Derived even though it's being pointed to by a Base *?" Yes, it can see that... And it might even forgo the virtual call in that case... But in most cases the compiler CANNOT see that.

    Here, maybe this will hammer it in:

    Code:
    int main()
    {
        Base *b;
    
        if (rand() % 2 == 0)
            b = new Base();
        else
            b = new Derived();
    
        b->Print();
    }
    Okay, NOW the compiler CANNOT possibly know the type of the object -- I'm determining it RANDOMLY. In THIS case how is the compiler supposed to know what type the object is?
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

Popular pages Recent additions subscribe to a feed