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