Възможно ли е да се приложи заобикаляне на vtable за виртуални функции?

Вместо да използвате виртуални функции, където има търсене към указателя на vtable в обекта, който след това ви отвежда до vtable, съдържащ указател към функцията - не би ли било възможно просто да съдържа член на данни в обекта, който сочи директно към функцията?


person user997112    schedule 24.11.2014    source източник
comment
Ако компилаторът знае статичния тип на обекта, той може да оптимизира извикването да бъде директно извикване. Мисля, че тази оптимизация е често срещана, но не съм 100% сигурен. Ако компилаторът не може да направи това, тогава наличието на ваш собствен указател в обекта (и какъвто и код да се нуждаете, за да се възползвате от него) вероятно ще бъде много по-малко оптимизирано, отколкото компилаторът просто да използва указателя във vtable.   -  person Michael Burr    schedule 24.11.2014
comment
Какъв би бил размерът на вашите обекти, ако добавите всички указатели във всеки?   -  person quantdev    schedule 24.11.2014
comment
което означава, че имате за всеки виртуален член член с данни във всеки екземпляр. Това е малко по-бързо, но много по-голямо по размер. Спестявате само една ненасоченост за повикването, но получавате огромно количество загубено пространство за данни.   -  person Klaus    schedule 24.11.2014
comment
Добре, кажи, че моите обекти имат само един метод за виртуален интерфейс. Това решение би ли било по-добро? Всъщност дори не трябва да съхранявате 8-байтов указател, можете просто да съхранявате отместване от член на данните.   -  person user997112    schedule 24.11.2014
comment
@user997112: не, трябва да е указател към функцията, която се съхранява в програмния код, функциите не са в пространството на обекта. Това ще изисква пълен 8-байтов указател. Шансовете са, че ако имате виртуална функция, имате нужда и от виртуален деструктор, така че ще ви трябват поне тези два 8-байтови указателя.   -  person Mooing Duck    schedule 25.11.2014
comment
Свързани: stackoverflow.com/questions/5417829/   -  person Mooing Duck    schedule 25.11.2014


Отговори (1)


Ако разбирам въпроса ви, вие търсите начин да реализирате полиморфизъм с помощта на указател на функция.

Е, възможно е, но изключително тромаво, податливо на грешки, но ще бъде трудно да надминете 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 lookup не е ли достатъчно производителен?

Ами търсенето на vtable основно зарежда указател на функция от фиксирано отместване, изчислено от компилатора. Кодът на асемблер за извикване на функция на визуален тест() би изглеждал така:

 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 е поне толкова ефективно, колкото извикването чрез указател на функция. Компилаторът се грижи за цялата инициализация и най-сложните ситуации на наследяване. По-добре използвайте мощните виртуални функции, вместо да се опитвате ръчно да превъзхождате своя компилатор.

person Christophe    schedule 24.11.2014
comment
Без да включите оптимизацията, този тест всъщност не демонстрира нищо полезно. - person Mark Ransom; 25.11.2014
comment
@MarkRansom най-доброто вероятно би било да се направи бенчмарк за конкретния случай на OP, защото не можете да обобщите нито от оптимизациите, извършени върху прекалено опростен демо код. - person Christophe; 25.11.2014