How does the compiler know which entry in vtable corresponds to a called virtual function?

Viewed 64

First of all, This question sounds to be repetitive but I explored this over google, but didn't get satisfactory answer as a layman

Question: Let's say we have 3 virtual function in the parent class and derived class as shown in below example

class Base1 {
    public :
    virtual void Print1() {
       cout << "Print1 Base1" << endl;
    }
    virtual void Print2() {
       cout << "Print1 Base1" << endl;
    }
    virtual void Print3() {
       cout << "Print1 Base1" << endl;
    }
};

class Derived : public Base1 {
public:  
    void Print1() {
       cout << "Print1 Derived1" << endl;
    }
    void Print2() {
       cout << "Print1 Derived2" << endl;
    }
    void Print3() {
       cout << "Print1 Derived3" << endl;
    }
};

int main()
{
    Derived d;
    Base1 *bptr = &d;  
    bptr->Print2(); // -> Here Derived::Print2() shall be called
    return 0;
 }

As shown in above example , Derived::Print2() shall be called in main()

Here , Base class and Derived class vTable shall consist of 3 entries for 3 virtual functions

 [0] -> Address of Print1()
 [1] -> Address of Print2()
 [2] -> Address of Print3()

I wanted to know , At runtime how compiler searches Print2() in Derived vTable ?

2 Answers

There is no searching - the table is not by name but by offset (i.e. it is an array).

In practice, it usually works a lot like this very simplified C-like implementation:

// Simplified type; the actual implementation doesn't need to care about the type.
typedef void(*function)(void*);

// Base class
struct Base1 {
    function* vtable;
};

// Derived class
struct Derived
{
    Base1 parent; // The base class subobject    
};

// Virtual members
void Base1_Print1(void*) {
    cout << "Print1 Base1" << endl;
}

void Base1_Print2(void*) {
    cout << "Print2 Base1" << endl;
}

// v-table
function Base1_vtable[2] = { &Base1_Print1, &Base1_Print2 };

// Constructor
void Base1_create(Base1* self)
{
    self->vtable = Base1_vtable;
}

// Overridden members
void Derived_Print2(void*) {
    cout << "Print2 Derived" << endl;
}

// v-table
function Derived_vtable[2] = { &Base_Print1, &Derived_Print2 };

// Constructor
void Derived_create(Derived* self)
{
    Base1_create(&self->parent); // Construct the base subobject
    self->parent.vtable = Derived_vtable; // Adjust the vtable
}

int main()
{
    Derived d;
    Derived_create(&d); // Construct d
    Base1 *bptr = &d.parent; // Implicit conversion made explicit.
    (bptr->vtable[0])(bptr); // Virtual call to Base_Print1
    (bptr->vtable[1])(bptr); // Virtual call to Derived_Print2
 }

It doesn't search. The correspondence "Print2 is the second entry in the vtable" is established at compile time, when the compiler looks at the definition of the Base1 class.

Related