昨天用cocos2d-x的时候遇到了一个疑问,当然运行是没问题的,由于以前没有过多的使用多继承,所以也没往深处想,可是函数调用的时候却怎么也想不通。

当时的情况是这样的,写了一个自己的精灵,继承与CCSprite和CCTouchDelegate,然后该精灵会注册触摸,简单说就是一个CCTouchDelegate指针指向了我new的自己的精灵对象,然后供cocos2d-x调用CCTouchDelegate里面重写的事件。

一切都那么的顺理成章,的确理论上是这样的,实际上也是这样的,可是仔细一想,由于我自己的精灵是一个多继承对象,CCTouchDelegate的指针实际上是指向了我自己精灵的CCTouchDelegate部分,即CCTouchDelegate指针的值不等于我自己精灵指针的值,这也是复合理论的,可在cocos2d-x里处理事件时,它会自动调用我这个CCTouchDelegate的触摸虚函数,这时候问题就来了,按MS编译器的调用方式,即会压入CCTouchDelegate指针入ecx,然后寻找虚函数表内重写的触摸事件的函数,该函数已被我重写,即调用了我自己重写的函数,然后我们关注一下这个重写的函数,该函数会调用CCSprite的函数与数据,而前面压入ecx的值是CCTouchDelegate的指针,这不是不对了么!怎么用CCTouchDelegate的指针来访问CCSprite呢?

上面是问题的引发。

为了解决这个问题,做了些小测试。

class BaseOne
{
public:
	BaseOne()
	{
		m_nCount = 1;
	}
	virtual ~BaseOne(){}
 
	void Add(int _nNumber)
	{
		m_nCount += _nNumber;
	}
 
protected:
	int m_nCount;
};
 
class BaseTwo
{
public:
	BaseTwo()
	{
		m_nSecCount = 2;
	}
	virtual ~BaseTwo(){}
 
	virtual void DoSecond()
	{
		void* ptr = this;
		++m_nSecCount;
	}
 
protected:
	int m_nSecCount;
};
 
class Deriver : public BaseOne, public BaseTwo
{
public:
	Deriver()
	{
		m_nThird = 0;
	}
	
	virtual void DoSecond()
	{
		void* ptr = this;
		--m_nSecCount;
		--m_nThird;
		--m_nCount;
	}
 
protected:
	int m_nThird;
};
 
int _tmain(int argc, _TCHAR* argv[])
{
	Deriver* pDer = new Deriver;
	BaseOne* pOne = pDer;
	BaseTwo* pTwo = pDer;
 
	printf("Der %08x\nOne %08x\nTwo %08x",
		pDer, pOne, pTwo);
 
	pTwo->DoSecond();
 
	return 0;
}

在main函数内,派生类的地址,基类1的地址,基类2的地址都是符合多继承的设计的,即我继承了两个基类,则有2个虚函数表。当我调用DoSecond函数时候,我看了下ecx,该值居然是基类2的地址,而不是派生类的地址! 可是我在派生类重写的虚函数内,还访问了基类1的数据!这是怎么回事呢?

看下虚函数的汇编码:

void* ptr = this;
00412213  mov         eax,dword ptr [ebp-8] 
00412216  sub         eax,8 
00412219  mov         dword ptr [ebp-14h],eax 
		--m_nSecCount;
0041221C  mov         eax,dword ptr [ebp-8] 
0041221F  mov         ecx,dword ptr [eax+4] 
00412222  sub         ecx,1 
00412225  mov         edx,dword ptr [ebp-8] 
00412228  mov         dword ptr [edx+4],ecx 
		--m_nThird;
0041222B  mov         eax,dword ptr [ebp-8] 
0041222E  mov         ecx,dword ptr [eax+8] 
00412231  sub         ecx,1 
00412234  mov         edx,dword ptr [ebp-8] 
00412237  mov         dword ptr [edx+8],ecx 
		--m_nCount;
0041223A  mov         eax,dword ptr [ebp-8] 
0041223D  mov         ecx,dword ptr [eax-4] 
00412240  sub         ecx,1 
00412243  mov         edx,dword ptr [ebp-8] 
00412246  mov         dword ptr [edx-4],ecx 

原来如此!按着this指针来逆向偏移访问数据了!

从派生类指针来调用基类2的虚函数,和基类2指针调用虚函数,它们要实现的效果应该是一样的,即传入函数的this指针地址得是派生类的地址。则有两种方案,第一种 派生类虚函数接受的this指针直接为派生类指针,第二种 派生类接收基类2的基类指针。由于需要由基类2直接调用派生类改写的虚函数,故方法1不能够使用,则只能使用方法2,故无论基类2调用虚函数,还是派生类调用虚函数,该虚函数的ecx都为基类2的指针,改写的虚函数由于已知是多继承模式,可以由基类2指针逆向获得派生类的指针。

故总结虚函数多态的MS编译器的实现方式:

非多继承,压入对象指针,查虚函数表调用函数

多继承,判断调用哪一个基类的虚函数,假如是基类1,则压入对象指针,如基类2,则进行偏移,压入对象指针,调用虚函数。

共 0 条回复
暂时没有人回复哦,赶紧抢沙发
发表新回复

作者

sryan
today is a good day