现在的位置: 首页 > 综合 > 正文

从指向类成员函数的指针看指针的“类型”

2013年09月09日 ⁄ 综合 ⁄ 共 3472字 ⁄ 字号 评论关闭

事情起源于最近编程中出现的一个编译错误,该错误如下:
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
为了不把自己程序中的细节问题牵扯进来,下面写一个可以导致同样错误的示例代码:

#include <iostream>
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类型的定义也要改成一个普通的
全局函数指针的定义形式:

class CTest {
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
;
}

下面给这个例子中再添点东西进去,加深理解。

#include <iostream>
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。指针重要的永远都是它所指向的那个东西,而不是指针本身。
指针只代表着别人对它所指向的东西的一种解释而已,只是一个逻辑上的解释而已。
用这种对指针的观点去理解虚函数的多态性,简直就是理所当然。

补充一点,既然指针是这样的,而且也已经知道类的成员函数其实也不属于对象,那
么当然是有方法在类没有实例化的时候调用到类的成员函数的,前提是这个成员函数
没有操作类的数据成员,下面就上面的例子改一下来实现这种调用:

#include <iostream>
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
;
}

抱歉!评论已关闭.