`
weiyinchao88
  • 浏览: 1185616 次
文章分类
社区版块
存档分类
最新评论

类的成员函数指针(比较深入)

 
阅读更多

From:http://blog.csdn.net/hairetz/archive/2009/05/06/4153252.aspx

个人感觉对于类的成员函数指针这块讲解的比较深入详细

推荐阅读

/////////////////////////////////////////////////

先看这样一段代码

class test
{
public:
test(int i){ m_i=i;}
test(){}


void hello()
{
printf("hello/n");
}
private:
int m_i;
};

int main()
{
test *p=new test();
p->hello();
p=NULL;
p->hello();
}

结果是:

hello

hello

为何

p=NULL;
p->hello();
这样之后,NULL->hello()也依然有效呢?

我们第一反应一定是觉得类的实例的成员函数,不同于成员变量,成员函数全部都存在同一个地方,所以的类的实例来调用的时候,一定是调用相同的函数指针。(想想也是有道理的,成员函数都是一样的功能,根本不需要实例化多个)

于是我们把代码改成这样

class test
{
public:
test(int i){ m_i=i;}
test(){}


void hello()
{
printf("hello/n");
}


private:
int m_i;
};


typedef void (test::*HELLO_FUNC)();

int main()
{
test *p=new test();
test q;
p->hello();
HELLO_FUNC phello_fun=&test::hello;
printf("%p/n",phello_fun);
p=NULL;
phello_fun=&test::hello;
printf("%p/n",phello_fun);
phello_fun=p->hello;
printf("%p/n",phello_fun);
phello_fun=q.hello;
printf("%p/n",phello_fun);
p->hello();
}

结果是:

hello
00401005
00401005
00401005
00401005
hello
Press any key to continue

也就是说不管是&test::hello,还是p->hello,或者q.hello,甚至于NULL->hello.

调用的地址都是0x00401005,也基本印证了我们的猜想。

事情到这里算是完了么?没有。

有人问道这样一段代码:

SSVector& SSVector::assign2product4setup(const SVSet& A, const SSVector& x)
{
int ret_val=

pthread_create(&pt,NULL,(void(*)(void*))SSVector::prefun,x);
}

void* SSVector::prefun (void* arg){
const SSVector &tx =*((SSVector*) arg);
}

行报错:invalid conversion from 'void (*)(void*)' to 'void* (*)(void*)'

pthread_create我就不解释了,第3个参数是线程函数的指针,为何这里报错呢?

说明普通的类成员函数的指针(如果它有函数指针的话),不同于一般的函数指针。

看看下面这篇文章关于这个问题的分析:

前言:在CSDN论坛经常会看到一些关于类成员函数指针的问题,起初我并不在意,以为成员函数指针和普通的函数指针是一样的,没有什么太多需要讨论的。当我找来相关书籍查阅了一番以后,突然意识到我以前对成员函数指针的理解太过于幼稚和肤浅了,它即不像我以前认为的那样简单,它也不像我以前认为的那样"默默无闻"。强烈的求知欲促使我对成员函数进行进一步的学习并有了这篇文章。

一。理论篇
在进行深入学习和分析之前,还是先看看书中是怎么介绍成员函数的。总结一下类成员函数指针的内容,应该包含以下几个知识点:
1
。成员函数指针并不是普通的函数指针。
2
。编译器提供了几个新的操作符来支持成员函数指针操作:

1)操作符"::*"用来声明一个类成员函数指针,例如:
typedefvoid(Base::*PVVBASEMEMFUNC)(void);//Baseisaclass
2)
操作符"->*"用来通过对象指针调用类成员函数指针,例如:
//pBaseisaBasepointerandwellinitialized
//pVIBaseMemFuncisamemberfunctionpointerandwellinitialized

(pBase->*pVIBaseMemFunc)();

3)操作符".*"用来通过对象调用类成员函数指针,例如:
//baseObjisaBaseobject
//pVIBaseMemFuncisamemberfunctionpointerandwellinitialized

(baseObj.*pVIBaseMemFunc)();


3
。成员函数指针是强类型的。

typedefvoid(Base::*PVVBASEMEMFUNC)(void);
typedef
void(Derived::*PVVDERIVEMEMFUNC)(void);
PVVBASEMEMFUNC
PVVDERIVEMEMFUNC是两个不同类型的成员函数指针类型。


4
。由于成员函数指针并不是真真意义上的指针,所以成员函数指针的转化就受限制。具体的转化细节依赖于不同的编译器,甚至是同一个编译器的不同版本。不过,处于同一个继承链中的不同类之间override的不同函数和虚函数还是可以转化的。

void*pVoid=reinterpret_cast<void*>(pVIBaseMemFunc); //error
int*pInt=reinterpret_cast<int*>(pVIBaseMemFunc); //error
pVIDeriveMemFunc=static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc); //OK


二。实践篇
有了上面的理论知识,我们对类成员函数指针有了大概的了解,但是我们对成员函数指针还存在太多的疑惑。既然说成员函数指针不是指针,那它到底是什么东东? 编译器为什么要限制成员函数指针转化?老办法,我们还是分析汇编代码揭示其中的秘密。首先,我写了这样两个具有继承关系的类:
接着,我又定义了一些成员函数指针类型:
最后,在main函数写了一些测试代码:
成功编译后生成汇编代码。老规矩,在分析汇编代码的过程中还是只分析对解决问题有意义的汇编代码,其他的就暂时忽略。
1
。成员函数指针不是指针。从代码看出,在main函数的调用栈(calling stack)中首先依次压入四个成员函数指针,如果它们是普通指针的话,它们之间的偏移量应该是4个字节,可是实际的情况却是这样的:

”The implementation of the pointer to member function must store within itself information as to whether the member function to which it refers is virtual or nonvirtual, information about where to find the appropriate virtual function table pointer (see The Compiler Puts Stuff in Classes [11, 37]), an offset to be added to or subtracted from the function's this pointer (see Meaning of Pointer Comparison [28, 97]), and possibly other information. A pointer to member function is commonly implemented as a small structure that contains this information, although many other implementations are also in use. Dereferencing and calling a pointer to member function usually involves examining the stored information and conditionally executing the appropriate virtual or nonvirtual function calling sequence.“

2
。成员函数指针的转化。本文所采用的代码是想比较普通成员函数指针和虚函数指针在转化的过程中存在那些差异:
对于符号”??_9@$B3AE“,我又找到了这样的汇编代码: 由此可以看出,对于虚函数,即使是用过成员函数指针间接调用,仍然具有和直接调用一样的特性。

;PVIBASEMEMFUNCpVIBaseMemFunc=&Base::setValue;
movDWORDPTR_pVIBaseMemFunc$[ebp],OFFSETFLAT:?setValue@Base@@QAEXH@Z;
取出Base::setValue函数的地址,存放于变量pVIBaseMemFunc所占内存的前4个字节(DWORD)中。

;PVVBASEMEMFUNCpVVBaseMemFunc=&Base::foobar;
movDWORDPTR_pVVBaseMemFunc$[ebp],OFFSETFLAT:??_9@$B3AE;`vcall'
取出符号”??_9@$B3AE“的值,存放于变量pVVBaseMemFunc所占内存的前4个字节(DWORD)中。

_TEXTSEGMENT
??_9@$B3AEPROCNEAR;`vcall',COMDAT
moveax,DWORDPTR[ecx]
jmpDWORDPTR[eax+4]
??_9@$B3AEENDP;`vcall'
_TEXTENDS
符号”??_9@$B3AE“代表的应该是一个存根函数,这个函数首先根据this指针获得虚函数表的指针,然后将指令再跳转到相应的虚函数的地址。

;PVIDERIVEMEMFUNCpVIDeriveMemFunc=static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc);
moveax,DWORDPTR_pVIBaseMemFunc$[ebp]
movDWORDPTR_pVIDeriveMemFunc$[ebp],eax
直接将变量pVIBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量_pVIDeriveMemFunc所占内存的前4个字节中。

;PVVDERIVEMEMFUNCpVVDeriveMemFunc=static_cast<PVVDERIVEMEMFUNC>(pVVBaseMemFunc);
moveax,DWORDPTR_pVVBaseMemFunc$[ebp]
movDWORDPTR_pVVDeriveMemFunc$[ebp],eax
直接将变量pVVBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量pVVDeriveMemFunc所占内存的前4个字节中。

由此可以看出,基类的成员函数指针转化到相应的派生类的成员函数指针,值保持不变。当然这里的例子继承关系相对来说比较简单,如果存在多继承和虚继承的情况下,结果可能会复杂的多。

3。函数调用
下面的函数调用都大同小异,这里是列出其中的一个: 这里的汇编代码并没有给我们太多新鲜的内容:将对象的首地址(this指针)存放于寄存器ECX中,接着就将指令转到变量_pVIBaseMemFunc所占内存的前4个字节所表示的地址。

到了这里,我们应该对成员函数指针有了进一步的了解。

;(baseObj.*pVIBaseMemFunc)(10);
movesi,esp
push10;0000000aH
leaecx,DWORDPTR_baseObj$[ebp]
callDWORDPTR_pVIBaseMemFunc$[ebp]
cmpesi,esp
call__RTC_CheckEsp


由此可以看出,他们之间的偏移量是12个字节。这12个字节中应该可以包含三个指针,其中的一个指针应该指向函数的地址,那另外两个指针又指向那里呢?在《C++ Common Knowledge: Essential Intermediate Programming(中文译名:

C++必知必会)这本书的第16章对这部分的内容做了说明,这个12个字节的偏移量正好印证了书中的内容:

classBase {
public:
//ordinarymemberfunction
voidsetValue(intiValue);

//virtualmemberfunction
virtualvoiddumpMe();
virtualvoidfoobar();

protected:
intm_iValue;
};

classDerived:publicBase{
public:
//ordinarymemberfunction
voidsetValue(intiValue);

//virtualmemberfunction
virtualvoiddumpMe();
virtualvoidfoobar();
private:
doublem_fValue;
};

typedefvoid(Base::*PVVBASEMEMFUNC)(void);
typedef
void(Derived::*PVVDERIVEMEMFUNC)(void);
typedef
void(Base::*PVIBASEMEMFUNC)(int);
typedef
void(Derived::*PVIDERIVEMEMFUNC)(int);

int_tmain(intargc,_TCHAR*argv[])
{
PVIBASEMEMFUNCpVIBaseMemFunc=&Base::setValue;
PVIDERIVEMEMFUNCpVIDeriveMemFunc=static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc);

PVVBASEMEMFUNCpVVBaseMemFunc=&Base::foobar;
PVVDERIVEMEMFUNCpVVDeriveMemFunc=static_cast<PVVDERIVEMEMFUNC>(pVVBaseMemFunc);

BasebaseObj;
(baseObj.*pVIBaseMemFunc)(10);
(baseObj.*pVVBaseMemFunc)();

DerivedderiveObj;
(deriveObj.*pVIDeriveMemFunc)(20);
(deriveObj.*pVVDeriveMemFunc)();

return0;
}

_deriveObj$=-88
_baseObj$=-60
_pVVDeriveMemFunc$=-44
_pVVBaseMemFunc$=-32
_pVIDeriveMemFunc$=-20
_pVIBaseMemFunc$=-8
_argc$=8
_argv$=12

分享到:
评论

相关推荐

    2023年美赛特等奖论文-F-2305794-解密.pdf

    大学生,数学建模,美国大学生数学建模竞赛,MCM/ICM,2023年美赛特等奖O奖论文

    亚太经社会:2024年亚太贸易便利化报告.pdf

    亚太经社会:2024年亚太贸易便利化报告.pdf

    消费者价格指数下的年通货膨胀率(1960-2021年).xls

    消费者价格指数(CPI):按消费者价格指数衡量的通货膨胀反映出普通消费者在指定时间间隔(如年度)内购买固定或变动的一篮子货物和服务的成本的年百分比变化。通常采用拉斯佩尔公式进行计算。

    node-v8.10.0-linux-x86.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    网页制作基础学习--HTML+CSS常用代码.txt

    网页制作基础学习--HTML+CSS常用代码

    IEC TS 60364-8-3-2020 低压电气装置.第8-3部分:功能方面.生产用户电气装置的操作.pdf

    IEC TS 60364-8-3-2020 低压电气装置.第8-3部分:功能方面.生产用户电气装置的操作.pdf

    智能制造数字化供应链全栈解决方案.pptx

    智能制造数字化供应链全栈解决方案.pptx

    yolov5训练自己的数据集.docx

    yolov5训练自己的数据集

    Windows 10系统上安装和配置Tomcat的步骤

    附件是Windows 10系统上安装和配置Tomcat的步骤,文件绿色安全,请大家放心下载,仅供交流学习使用,无任何商业目的!

    2018美赛O奖论文C题合集.pdf

    大学生,数学建模,美国大学生数学建模竞赛,MCM/ICM,历年美赛特等奖O奖论文

    超级实用简洁的反色软件,支持部分区域反色!!

    超级实用简洁的反色软件,支持部分区域反色!! 界面简洁,易于使用! 源码可以联系

    2024年中国NAD+抗衰补剂行业研究报告.docx

    2024年中国NAD+抗衰补剂行业研究报告

    高德地图API+Python解决租房问题内含源码和设计文档.md

    高德地图API+Python解决租房问题内含源码和设计文档.md

    QYResearch:2023年前10大壁纸刀企业占据全球39%的市场份额.docx

    QYResearch:2023年前10大壁纸刀企业占据全球39%的市场份额.docx

    广东工业大学-数据库简答题考试试题回忆版以及答案解析.doc

    此试题是考试后回忆版本,你会发现是惊喜。恭喜你考个好成绩。

    node-v9.11.0-linux-s390x.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    MVIMG_20240506_214323.jpg

    MVIMG_20240506_214323.jpg

    2023年美赛特等奖论文-B-2318300-解密.pdf

    大学生,数学建模,美国大学生数学建模竞赛,MCM/ICM,2023年美赛特等奖O奖论文

    数据库课程设计的概要介绍与分析

    数据库是计算机科学中的一个核心领域,它用于存储、管理和检索数据。随着信息技术的发展,数据库技术已经渗透到我们日常生活的方方面面,从简单的网站用户信息管理到复杂的企业级数据仓库系统,无一不彰显其重要性。以下是一个关于数据库的综合资源描述,旨在为初学者和专业人士提供一个全面的视角。 ### 1. 数据库基础 数据库的基础知识包括了解数据模型、数据库管理系统(DBMS)以及SQL语言等。数据模型主要有关系型模型(如MySQL、Oracle)、非关系型模型(NoSQL,如MongoDB、Cassandra)等。关系型数据库遵循ACID特性(原子性、一致性、隔离性、持久性),适用于需要高度一致性的场景;而非关系型数据库则以灵活的schema设计和高可扩展性见长,适用于大数据处理和实时Web应用。 ### 2. 学习资源 - **在线课程**:Coursera、edX、Udacity等平台提供了多门数据库相关的课程,涵盖从入门到高级的各种主题,比如Stanford大学的《数据库系统概念》课程。 - **书籍**:《数据库系统概论》(Silberschatz, Korth, Sudarsha

    《统计与数据分析基础》03数据处理.pptx

    《统计与数据分析基础》03数据处理

Global site tag (gtag.js) - Google Analytics