Engineer999
Engineer999

Reputation: 3955

Are Vtables only used with a pointer to base class

I know many questions are asked here about vtables but i'm still a bit confused.

Are vtables only used when we have a pointer to a base class to resolve which virtual function of derived classes to call?

In my example below, In case 1, are vtables used here at run time, even tho the Tiger object was not created dynamically on the heap / free store?

In case 2, are vtables used, even tho the compiler knows at compile time that we are pointing to a Tiger object.

What about case 3?

Thanks in advance.

#include <iostream>

using namespace std;

class Animal // base class
{
    public:
        virtual void makeNoise() {cout<<"  "<<endl;}
};

class Tiger: public Animal
{
    public:
        void makeNoise() {cout<<"Tiger Noise"<<endl;}
};

class Elephant: public Animal
{
    public:
        void makeNoise() {cout<<"Elephant Noise"<<endl;}
};

int main()
{
    //case 1
    Tiger t1;
    Animal* aptr = &t1;
    aptr->makeNoise(); // vtables used?

    //case 2
    Tiger t2;
    Tiger* tptr = &t2;  //vtables used ?
    tptr->makeNoise();

    //case 3
    Elephant e1;       //vtables used ?
    e1.makeNoise();

}

Upvotes: 2

Views: 949

Answers (3)

Sorush
Sorush

Reputation: 4129

It was interesting to me to see how they are compiled. So this is what I saw:

Clang 13 and GCC 11.2 create the same results. The assembly I mentioned below are from Clang:

With no optimisation:

case 1
aptr->makeNoise(); 
assembly: call    qword ptr [rax]
Call to pointer, therefore, vtable is used

case 2
tptr->makeNoise();
assembly: call    qword ptr [rax]
Call to pointer, therefore, vtable is used


//case 3
e1.makeNoise();
assembly: call    Elephant::makeNoise()
Direct call

Clang with -O1

case 1
aptr->makeNoise(); 
assembly: call    Tiger::makeNoise()
direct call

case 2
tptr->makeNoise();
assembly: call    Tiger::makeNoise()
direct call

case 3
e1.makeNoise();
assembly: call    Elephant::makeNoise()
Direct call

Further optimisations will make -O1 more optimised.

Upvotes: 0

Priera
Priera

Reputation: 1

As the other comments say, usage of vtables is handled by the compiler, who may try to optimize their access, as long as the produced output is the expected one.

However, we may think about vtables as tables that contain the addresses of the virtual methods. Each call to a method that has been declared "virtual" in a parent class, should check the vtable at runtime, in order to know the specific address where to jump.

This is the behaviour programmers expect, despite the specific mechanism may be more tricky, and even may not rely on querying vtables at all if compiler can determine the address at compile time.

So, in all these cases, the compiler may be clever enough to set the address at compile time. But you should just rely on that in the "worst case", an access to the vtable will be done in each case, since you're calling virtual methods -This is the expected behavior-, and let the compiler do the optimizations it thinks it has to do.

Just as a clarification about what you say in case 1, vtable access has nothing to about whether the object has been allocated in the heap or in the stack. These are completely different concepts.

Upvotes: 0

Whether a particular compiler uses a virtual function table or an entirely different mechanism to implement dynamic virtual function dispatch is up to that compiler's internal implementation. If you want an answer for a particular compiler's behaviour, consult the docs and/or source code of that compiler.

The C++ language itself defines how virtual function invocation must work, and leaves it up to the compiler to make it so.

What the standard requires is for the call of a virtual function to be dispatched to the final overrider, based on the dynamic type of the object on which the function is invoked. In your code, the dynamic type of t1 and t2 is Tiger, and the dynamic type of e1 is Elephant.

Yes, most (if not all) compilers use the virtual function table to implement virtual function invocations. Yes, any decent compiler should maximise its attempts to resolve dynamic dispatch at compile time if it is able to do so, and replace virtual-table-usage with direct invocation when it can (that is a quality-of-implementation issue for the compiler).

Which of the calls in your example will be statically dispatched depends on how "aggressive" (or "smart," if you prefer) the optimiser of your compiler is.

I would say that every sane compiler should statically dispatch the call through e1, even with optimisations disabled. It would be a totally unnecessary pessimisation to invoke the dynamic dispatch mechanism there.

As for the calls through aptr and tptr, it depends on whether the static analyser of your compiler's optimiser is capable of eliminating aptr and tptr, replacing them with use of the actual object they point to (since that information is avaiable at compile time). A decent optimiser should be capable of that and dispatch all 3 calls statically.

To be sure how your compiler handles the call, inspect the generated assembly.

Upvotes: 5

Related Questions