事情起源于最近编程中出现的一个编译错误,该错误如下:
D:/My Documents/My Projects/Pedump/SimplestDisassembler.cpp(23) :
error C2440: 'initializing' : cannot convert from
'bool (__thiscall CSimplestDisassembler::*)(void)' to
'bool (__cdecl *)(void)'
There is no context in which this conversion is possible
为了不把自己程序中的细节问题牵扯进来,下面写一个可以导致同样错误的示例代码:
using namespace std;
class CTest ...{
public:
bool func() ...{
cout << "CTest::func" << endl;
return true;
}
typedef bool (*pFunc)(void);
static pFunc pfn;
};
CTest::pFunc CTest::pfn = func;
int main() ...{
CTest::pfn();
return 0;
}
用VC6编译后会出现错误:
error C2440: 'initializing' : cannot convert from 'bool (__thiscall CTest::*)(void)' to 'bool (__cdecl *)(void)'
There is no context in which this conversion is possible
错误行是:
CTest::pFunc CTest::pfn = func;
从错误信息中一下子就能看出来,是指针类型不兼容。说明指针类型定义有问题。但这里
有个__thiscall的调用方式,自己以前学习C++不深入,没有见过,于是翻看MSDN:
在一篇名为"Argument Passing and Naming Conventions"
的文章中说道:
VC++支持以下调用方式:__cdecl,__stdcall,__fastcall,__thiscall。
这里就出现的问题主要看__thiscall。
MSDN上对这种调用方式的讲解是:由被调用者自己负责清栈,参数通过堆栈传递,
this指针存储在ECX寄存器中。这种调用方式是C++类成员函数默认的调用方式。
因为是被调用者自身负责清栈,所以编译器产生可变参数的__cdecl调用,并且this指针
最后入栈(从上次的示例中可以看到这一点)。
__thiscall调用方式不能在程序中显式地指定,因为它不是一个关键字。这种调用方式
仅用于C++,不能指定C名字修饰。
看到这里,以上的错误信息完全能明白了,要改正过来也很简单,只需要把
typedef bool (*pFunc)(void);
一句改为:
typedef bool (CTest::*pFunc)(void);
即可。至于为什么指向类成员函数的指针要这样来定义,自己的理解写在后面。
上面把错误改过来了,再次编译,出现新的错误,错误发生在
CTest::pFunc();
这一行,错误信息是:
error C2064: term does not evaluate to a function
现在来看为什么这样的调用形式会出现错误。都知道,类的成员函数就相当于一个普通
的全局函数,只不过使用__thiscall的调用方式,有一个隐含的this指针作为参数,
这个隐含的参数通过ECX寄存器来传递,通常在我们这样写程序时:
CTest test;
test.func();
编译器看到test.func()的调用,就会把test对象的地址放到ECX寄存器中,然后转存到
func函数的参数栈中。那么,看上面出错的那一行:CTest::pFunc,按理来说,类的成员
函数同样不属于对象,所有该类的对象共享同一份成员函数代码,既然如此,类的成员函数
当然应该可以在该类没有实例化的时候调用,但是没有对象的调用也就没有了this指针,
这个函数少一个参数,当然是调用不了了。上面出错的那一行,CTest::pFunc(),显然,
就是因为没有this参数。那么,改正的方法也出来了,要么给它一个this参数,要么
把这个函数应该有的那个this参数去掉,就是让它变成一个静态成员函数。
第一种改法,给它一个this参数:
CTest test;
(test.*CTest::pfn)();
编译运行成功。
第二种改法,把它的this参数去掉,但同时,pFunc类型的定义也要改成一个普通的
全局函数指针的定义形式:
public:
static bool func() {
cout << "CTest::func" << endl;
return true;
}
typedef
bool (*pFunc)(void);static pFunc pfn;
};
CTest::pFunc CTest::pfn
= func; int main() {CTest::pfn();
return 0;
}
下面给这个例子中再添点东西进去,加深理解。
using namespace std; class CTest {
public:
bool func() {
cout << "CTest::func" << endl;
return true;
}
bool func2() {
cout << "CTest::func2" << endl;
return true;
}
void walk() {
for(int i = 0; i < 2; i++) {
(this->*pfuncArray[i])();
}
}
typedef
bool (CTest::*pFunc)(void);static pFunc pfuncArray[];
};
CTest::pFunc CTest::pfuncArray[]
= {func, func2
}; int main() {
CTest test;
test.walk();
bool (CTest::*p)(void) = CTest::func;
(test.*p)();
return 0;
}
上面也看到了有点奇怪的形式:
CTest test;
(test.*CTest::pfn)();
或:
(this->*pfuncArray[i])();
或:
bool (CTest::*p)(void) = CTest::func;
(test.*p)();
尤其是第三个,p根本就不是CTest类的成员,但为什么还要这样调用呢,就是为了
传递一个this指针给p。指针重要的永远都是它所指向的那个东西,而不是指针本身。
指针只代表着别人对它所指向的东西的一种解释而已,只是一个逻辑上的解释而已。
用这种对指针的观点去理解虚函数的多态性,简直就是理所当然。
补充一点,既然指针是这样的,而且也已经知道类的成员函数其实也不属于对象,那
么当然是有方法在类没有实例化的时候调用到类的成员函数的,前提是这个成员函数
没有操作类的数据成员,下面就上面的例子改一下来实现这种调用:
using namespace std; class CTest {
public:
bool func() {
cout << "CTest::func" << endl;
return true;
}
typedef
bool (CTest::*pFunc)(void);static pFunc pfn;
};
CTest::pFunc CTest::pfn
= func; int main() {bool (CTest::*p)(void) = CTest::pfn;
__asm call p
return 0;
}