Possible to implement bypassing vtable for virtual functions?

706 views Asked by At

Instead of using virtual functions where there is a lookup to the vtable pointer in the object, which then takes you to the vtable, containing a pointer to the function- would it not be possible to just contain a data member in the object which points directly to the function?

1

There are 1 answers

2
Christophe On BEST ANSWER

If I understand your question, you are looking for a way to implement polymorphism using a function pointer.

Well, it is possible but extremely cumbersome, error prone, but it'll be difficult to outperform de generated by your compiler for virtual function calls.

How to do it ?

The idea is to use a function pointer. In order to implement polymorphims, it must be in the base class.

class B {                   // Base class
protected:
    void (B::*fptest)();    // Function pointer to member function
public:
    void mtest()            // Class specific mebmer function
    { cout << "Base test\n"; }  
    void itest()            // Polymorphic function
    { (this->*fptest)(); }        // implemented by calling the poitner to member function 
    B() : fptest(&B::mtest) { }   // Ctor must initialize the function pointer
    virtual ~B() {}        
};

class D : public B {         // Derived class 
public:
    void mtest()             // Class specific mebmer function
    { cout << "Derived test\n"; }
    D()                     // Ctor 
    { fptest = reinterpret_cast<void(B::*)()>(&D::mtest); }  // not sure it's this safe in case of multiple inheritance !!
};

Code to test this construct:

B b; 
D d;
B *pb = &b, *pd = &d;
pb->itest(); 
pd->itest();

Is it safe ?

There are severe limitations to this. For example:

  • You have to ensure that every derived class initializes correctly the function pointer.
  • In case of multiple inheritance, the function pointer cast to the base class member might not work as expected.
  • There is no overloading of pointers possible. So you'll need a different pointer for every possible signature. That could be pretty odd.

Is it more performant than vtable lookup ?

No: look at the assembler executed for every polymorphic call to itest():

; 41   :    pd->itest();  // cod for the call for a derived object
    mov ecx, DWORD PTR _pd$[ebp]        ; load the oject address
    call    ?itest@B@@QAEXXZ            ; call B::itest
; 16   :    void itest() { (this->*fptest)(); }
    push    ebp
    mov ebp, esp
    sub esp, 68                 ; 00000044H
    push    ebx
    push    esi
    push    edi
    mov DWORD PTR _this$[ebp], ecx   ; use address of object as parameter
    mov eax, DWORD PTR _this$[ebp]   ; load the function pointer 
    mov ecx, DWORD PTR _this$[ebp]   ;  "    "
    mov edx, DWORD PTR [eax+4]       ; call the function pointer 
    call    edx
    pop edi
    pop esi
    pop ebx
    mov esp, ebp
    pop ebp
    ret 0

Of course, the optimizer could inline the code, removing some push and pop but the general principle is that the code for the indirection will be generated.

Isn't vtable lookup performant enough ?

Well a vtable lookup is basically loading a function pointer from a fixed offset calculated by the compiler. The assembler code for callling a vitual test() function would look like this:

 39   :     pd->test(); 
    mov eax, DWORD PTR _pd$[ebp]
    mov edx, DWORD PTR [eax]
    mov ecx, DWORD PTR _pd$[ebp]
    mov eax, DWORD PTR [edx]
    call    eax

Conclusion

A vtable lookup is at least as performant that a call through a function pointer. The compiler takes care of all the initialisation and the most complex inheritance situations. Better use the powerfull of virtual functions instead of trying to manually outperform your compiler.