可以为虚拟功能实现绕过vtable吗?

时间:2014-11-24 20:29:17

标签: c++ polymorphism vtable

而不是使用虚拟函数,其中查找对象中的vtable指针,然后将其带到包含指向函数的指针的vtable,这样就不可能在对象中包含数据成员哪个直接指向函数?

1 个答案:

答案 0 :(得分:1)

如果我理解你的问题,那么你正在寻找一种使用函数指针实现多态的方法。

嗯,它可能但是非常麻烦,容易出错,但是对于虚函数调用来说,编译器生成的de会超出de。

怎么做?

想法是使用函数指针。为了实现多态,它必须在基类中。

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 !!
};

测试此构造的代码:

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

安全吗?

这有严重的局限性。例如:

  • 您必须确保每个派生类都正确初始化函数指针。
  • 在多重继承的情况下,强制转换为基类成员的函数指针可能无法按预期工作。
  • 指针不会超载。因此,您需要为每个可能的签名使用不同的指针。这可能很奇怪。

它比vtable查找更高效吗?

否:查看为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

当然,优化器可以内联代码,删除一些推送和弹出,但一般原则是将生成间接代码。

vtable查找性能不够吗?

vtable查找基本上是从编译器计算的固定偏移量中加载函数指针。用于调用vitual test()函数的汇编程序代码如下所示:

 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

结论

vtable查找至少与通过函数指针调用的性能相同。编译器负责所有初始化和最复杂的继承情况。更好地使用强大的虚拟函数,而不是尝试手动优于您的编译器。