Some framework code will use the class member function pointer to achieve some similar function callback function, and through the class member function pointer to call the class member function, is it possible to achieve the effect of polymorphism? This question is difficult to answer, or on the code to explore it

class Base
{
public:
	Base(void);
	~Base(void);
	void func();
	virtual void vfunc();
	void printvfuncaddress();
};
Base::Base(void)
{
}


Base::~Base(void)
{
}

void Base::func()
{
	cout << "Base::func()"<<endl;
}

void Base::vfunc()
{
	cout << "Base::vfunc()"<<endl;
}

void Base::printvfuncaddress()
{
	void** p = (void**)this;
	void* vtable = *p;

	cout<<"vtable address: "<< vtable<<endl;
	
	cout<<"vtable[0]: "<<((void**)vtable)[0]<<endl;

}
Copy the code

First, we define a Base class, which defines a common function func and a virtual function vfunc, and a function printvFuncAddress, which needs explaining. This is the address of the virtual function table of the output class, and the address of the first virtual function. Since there is only one virtual function in the class, this is actually the address of the vfunc function. The principle of C++ virtual function table is not explained here, you can refer to the relevant information.

Define a class Derived from Base like this:

class Derived :
	public Base
{
public:
	Derived(void);
	~Derived(void);
	void func();
	virtual void vfunc();
};

Derived::Derived(void)
{
}


Derived::~Derived(void)
{
}

void Derived::func()
{
	cout << "Derived::func()"<<endl;

}

void Derived::vfunc()
{
	cout << "Derived::vfunc()"<<endl;
}
Copy the code

Predictably, when the printvFuncAddress function is called from the Derived class, the output virtual function address is different from that of the Base class because the virtual function is overwritten.

Inside the main function, test the class’s member function pointer with the following code:

typedef void (Base::*Pfuntion)(); int _tmain(int argc, _TCHAR* argv[]) { Base base; Derived derived; // Pfuntion pfunc = &base ::func; (base.*pfunc)(); // Output "Base::func()" (derived.*pfunc)(); // Output "Base::func()" // vfunction pointer Pfuntion pvfunc = &base ::vfunc; (base.*pvfunc)(); // Output "Base::vfunc()" Base. Printvfuncaddress (); (derived.*pvfunc)(); / / output Derived: : vfunc "()" Derived. Printvfuncaddress (); return 0; }Copy the code

Typedefs void (Base::*Pfuntion)(); typedefs void (Base::*Pfuntion)(); typedefs void (Base::*Pfuntion) Base::func(); Base::func(); Base::func(); Base::func() The result should be the same in either object pair.

The second time we defined the pointer pvfunc and assigned &base ::vfunc. Base::vfunc() and Derived::vfunc() are called to Base and Derived. When a member function is called through a pointer to a member function, it is possible to achieve the effect of polymorphism. There seems to be no special place. Pvfunc = &base ::vfunc Pfuntion pvfunc = &base ::vfunc Pfuntion pvfunc = &base ::vfunc Pfuntion pvfunc = &base ::vfunc Pfuntion pvfunc = &base ::vfunc Because calls to base.*pvfunc)() and (derived.*pvfunc)() are not virtual function calls, the compiler does not know whether pvfunc refers to a virtual function or a normal function. This is a run-time matter, so it is impossible to do any compiler magic at this point. You can verify this with disassembly, just a normal function call.

(derived.*pvfunc)();
012E4A2B  mov         esi,esp  
012E4A2D  lea         ecx,[ebp-20h]  
012E4A30  call        dword ptr [ebp-38h]  
Copy the code

Tell me, wise man, why did this happen? Pfuntion pvfunc = &base ::vfunc: Pfuntion pvfunc = &base ::vfunc: Pfuntion pvfunc = &base ::vfunc

	Pfuntion pvfunc = &Base::vfunc;  
00294A0D  mov         dword ptr [ebp-38h],offset Base::`vcall'{0}' (2912A8h) 
Copy the code

[ebP-38h] pvfunc = pvfunc [ebP-38h] pvfunc = pvfunc Pvfunc (base.*pvfunc)(base.*pvfunc)

(base.*pvfunc)(); "Base:: vFUNc ()" 00294A14 mov ESI, ESP 00294A16 LEA ECx,[EBP-14h] 00294A19 Call dword PTR [EBP-38h] 00294A1C CMP esi,esp 00294A1E call @ILT+455(__RTC_CheckEsp) (2911CCh)Copy the code

Follow up call Dword PTR [EBP-38H] and it should go to 0x2912A8h

Base::`vcall'{0}':
002912A8  jmp         Base::`vcall'{0}' (291590h)  
Copy the code

There is only one JMP statement here, jump to 0x291590h and continue

Base::`vcall'{0}':
00291590  mov         eax,dword ptr [ecx]  
00291592  jmp         dword ptr [eax]  
Copy the code

Oh my god, there is another JMP, where is this going, in fact, this is the key, what is ecX? This pointer has the same value as the pointer to the virtual table, and the JMP above will execute to the first function in the virtual table, which, you can see, jumps to 0x0029100A

Base::vfunc:
0029100A  jmp         Base::vfunc (291660h)
Copy the code

Here’s another IMP, followed by a call to 0x00291660, which is the implementation of Base::vfunc.

void Base::vfunc() { 00291660 push ebp 00291661 mov ebp,esp 00291663 sub esp,0CCh 00291669 push ebx 0029166A push esi 0029166B push edi 0029166C push ecx 0029166D lea edi,[ebp-0CCh] 00291673 mov ecx,33h 00291678 mov eax,0CCCCCCCCh 0029167D rep stos dword ptr es:[edi] 0029167F pop ecx 00291680 mov dword ptr [ebp-8],ecx cout << "Base::vfunc()"<<endl; 00291683 mov esi,esp ... . }Copy the code

Based on the above analysis, pvfunc is 0x2912A8h, which is finally called to Base::vfunc() through the layers of 0x2912A8h-> 0x291590H ->0x0029100A(virtual function address)-> 0x00291660H. (derived.*pvfunc)()

(derived.*pvfunc)(); "Derived::vfunc()" 00294A2B mov ESI, ESP 00294A2D Lea ECx,[EBP-20h] 00294A30 Call Dword PTR [EBP-38h]Copy the code

Again, 0x2912A8h,

Base::`vcall'{0}':
002912A8  jmp         Base::`vcall'{0}' (291590h)  
Copy the code

Then JMP goes to 0x291590h

Base::`vcall'{0}':
00291590  mov         eax,dword ptr [ecx]  
00291592  jmp         dword ptr [eax] 
Copy the code

It is then called to 0x002912B7 from the virtual function table, Derived as follows:

Derived::vfunc:
002912B7  jmp         Derived::vfunc (292080h)
Copy the code

Continue to call 0x292080h, and that’s the concrete implementation of Derived::vfunc

void Derived::vfunc() { 00292080 push ebp 00292081 mov ebp,esp 00292083 sub esp,0CCh 00292089 push ebx 0029208A push esi  0029208B push edi 0029208C push ecx 0029208D lea edi,[ebp-0CCh] 00292093 mov ecx,33h 00292098 mov eax,0CCCCCCCCh 0029209D rep stos dword ptr es:[edi] 0029209F pop ecx 002920A0 mov dword ptr [ebp-8],ecx cout << "Derived::vfunc()"<<endl; 002920A3 mov esi,esp ... . }Copy the code

Pvfunc has a value of 0x2912A8h, and the Derived::vfunc() is finally called by layer upon layer of calls to 0x2912A8h-> 0x291590H ->0x002912B7(virtual function address)->0x292080.

Pfuntion pvfunc = &base ::vfunc does not assign the address of the virtual function to pvfunc. The real address of the virtual function is printed with printvFuncAddress. It can be seen that 0x0029100A and 0x002912B7 are the same as our analysis above.

Base::func()
Base::func()
Base::vfunc()
vtable address: 00297834
vtable[0]: 0029100A
Derived::vfunc()
vtable address: 00297860
vtable[0]: 002912B7
Copy the code

That is, pvfunc actually points to a compiler-generated “pile function” that looks up and calls real member functions based on the virtual function table, thus achieving polymorphism.