- 浏览: 2053157 次
- 性别:
- 来自: 北京
-
文章分类
- 全部博客 (651)
- ACE (35)
- BAT (9)
- C/C++ (116)
- fast-cgi (14)
- COM (27)
- python (59)
- CGI (4)
- C# (2)
- VC (84)
- DataBase (29)
- Linux (96)
- P2P (6)
- PHP (15)
- Web (6)
- Memcached (7)
- IME输入法 (11)
- 设计模式 (2)
- 搜索引擎 (1)
- 个人情感 (4)
- 笔试/面试 (3)
- 一亩三分地 (33)
- 历史 (2)
- 地理 (1)
- 人物 (3)
- 经济 (0)
- 不仅仅是笑哦 (43)
- 小故事大道理 (2)
- http://www.bjdsmyysjk120.com/ (0)
- http://www.bjdsmyy120.com/ (0)
- 它山之石可以攻玉 (15)
- 大学生你关注些什么 (28)
- 数据恢复 (1)
最新评论
-
luokaichuang:
这个规范里还是没有让我明白当浏览器上传文件时,STDIN的消息 ...
FastCGI规范 -
effort_fan:
好文章!学习了,谢谢分享!
com技术简介 -
vcell:
有错误os.walk(strPath)返回的已经是全部的文件和 ...
通过python获取目录的大小 -
feifeigd:
feifeigd 写道注意:文章中的CPP示例第二行 #inc ...
ATL入门:利用ATL编写简单的COM组件 -
feifeigd:
注意:文章中的CPP示例第二行 #include " ...
ATL入门:利用ATL编写简单的COM组件
From:http://blog.csdn.net/hairetz/archive/2009/05/06/4153252.aspx 个人感觉对于类的成员函数指针这块讲解的比较深入详细 推荐阅读 ///////////////////////////////////////////////// 先看这样一段代码 class test int main() 结果是: hello hello 为何 p=NULL; 我们第一反应一定是觉得类的实例的成员函数,不同于成员变量,成员函数全部都存在同一个地方,所以的类的实例来调用的时候,一定是调用相同的函数指针。(想想也是有道理的,成员函数都是一样的功能,根本不需要实例化多个) 于是我们把代码改成这样 class test int main() 结果是: hello 也就是说不管是&test::hello,还是p->hello,或者q.hello,甚至于NULL->hello. 调用的地址都是0x00401005,也基本印证了我们的猜想。 事情到这里算是完了么?没有。 有人问道这样一段代码: SSVector& SSVector::assign2product4setup(const SVSet& A, const SSVector& x) pthread_create(&pt,NULL,(void(*)(void*))SSVector::prefun,x); pthread_create我就不解释了,第3个参数是线程函数的指针,为何这里报错呢? 说明普通的类成员函数的指针(如果它有函数指针的话),不同于一般的函数指针。 看看下面这篇文章关于这个问题的分析: 前言:在CSDN论坛经常会看到一些关于类成员函数指针的问题,起初我并不在意,以为成员函数指针和普通的函数指针是一样的,没有什么太多需要讨论的。当我找来相关书籍查阅了一番以后,突然意识到我以前对成员函数指针的理解太过于幼稚和肤浅了,它即不像我以前认为的那样简单,它也不像我以前认为的那样"默默无闻"。强烈的求知欲促使我对成员函数进行进一步的学习并有了这篇文章。 1) 操作符"::*"用来声明一个类成员函数指针,例如: typedef void (Base::*PVVBASEMEMFUNC)(void); void* pVoid = reinterpret_cast<void*>(pVIBaseMemFunc); //error ”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.“ ; PVIBASEMEMFUNC pVIBaseMemFunc = &Base::setValue; ; PVVBASEMEMFUNC pVVBaseMemFunc = &Base::foobar; _TEXT SEGMENT ; PVIDERIVEMEMFUNC pVIDeriveMemFunc = static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc); ; PVVDERIVEMEMFUNC pVVDeriveMemFunc = static_cast<PVVDERIVEMEMFUNC>(pVVBaseMemFunc); 由此可以看出,基类的成员函数指针转化到相应的派生类的成员函数指针,值保持不变。当然这里的例子继承关系相对来说比较简单,如果存在多继承和虚继承的情况下,结果可能会复杂的多。 3。函数调用 ; (baseObj.*pVIBaseMemFunc)(10); C++必知必会)这本书的第16章对这部分的内容做了说明,这个12个字节的偏移量正好印证了书中的内容: class Base { typedef void (Base::*PVVBASEMEMFUNC)(void); int _tmain(int argc, _TCHAR* argv[]) _deriveObj$ = -88
{
public:
test(int i){ m_i=i;}
test(){}
void hello()
{
printf("hello\n");
}
private:
int m_i;
};
{
test *p=new test();
p->hello();
p=NULL;
p->hello();
}
p->hello(); 这样之后,NULL->hello()也依然有效呢?
{
public:
test(int i){ m_i=i;}
test(){}
void hello()
{
printf("hello\n");
}
private:
int m_i;
};
typedef void (test::*HELLO_FUNC)();
{
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();
}
00401005
00401005
00401005
00401005
hello
Press any key to continue
{
int ret_val=
}
void* SSVector::prefun (void* arg){
const SSVector &tx =*((SSVector*) arg);
}
行报错:invalid conversion from 'void (*)(void*)' to 'void* (*)(void*)'
一。理论篇
在进行深入学习和分析之前,还是先看看书中是怎么介绍成员函数的。总结一下类成员函数指针的内容,应该包含以下几个知识点:
1。成员函数指针并不是普通的函数指针。
2。编译器提供了几个新的操作符来支持成员函数指针操作:
typedef void (Base::*PVVBASEMEMFUNC)(void); //Base is a class
2) 操作符"->*"用来通过对象指针调用类成员函数指针,例如:
//pBase is a Base pointer and well initialized
//pVIBaseMemFunc is a member function pointer and well initialized
(pBase->*pVIBaseMemFunc)();
3) 操作符".*"用来通过对象调用类成员函数指针,例如:
//baseObj is a Base object
//pVIBaseMemFunc is a member function pointer and well initialized
(baseObj.*pVIBaseMemFunc)();
3。成员函数指针是强类型的。
typedef void (Derived::*PVVDERIVEMEMFUNC)(void);
PVVBASEMEMFUNC和PVVDERIVEMEMFUNC是两个不同类型的成员函数指针类型。
4。由于成员函数指针并不是真真意义上的指针,所以成员函数指针的转化就受限制。具体的转化细节依赖于不同的编译器,甚至是同一个编译器的不同版本。不过,处于同一个继承链中的不同类之间override的不同函数和虚函数还是可以转化的。
int* pInt = reinterpret_cast<int*>(pVIBaseMemFunc); //error
pVIDeriveMemFunc = static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc); //OK
二。实践篇
有了上面的理论知识,我们对类成员函数指针有了大概的了解,但是我们对成员函数指针还存在太多的疑惑。既然说成员函数指针不是指针,那它到底是什么东东? 编译器为什么要限制成员函数指针转化?老办法,我们还是分析汇编代码揭示其中的秘密。首先,我写了这样两个具有继承关系的类:
接着,我又定义了一些成员函数指针类型:
最后,在main函数写了一些测试代码:
成功编译后生成汇编代码。老规矩,在分析汇编代码的过程中还是只分析对解决问题有意义的汇编代码,其他的就暂时忽略。
1。成员函数指针不是指针。从代码看出,在main函数的调用栈(calling stack)中首先依次压入四个成员函数指针,如果它们是普通指针的话,它们之间的偏移量应该是4个字节,可是实际的情况却是这样的:
2。成员函数指针的转化。本文所采用的代码是想比较普通成员函数指针和虚函数指针在转化的过程中存在那些差异:
对于符号”??_9@$B3AE“,我又找到了这样的汇编代码: 由此可以看出,对于虚函数,即使是用过成员函数指针间接调用,仍然具有和直接调用一样的特性。
mov DWORD PTR _pVIBaseMemFunc$[ebp], OFFSET FLAT:?setValue@Base@@QAEXH@Z ;
取出Base::setValue函数的地址,存放于变量pVIBaseMemFunc所占内存的前4个字节(DWORD)中。
mov DWORD PTR _pVVBaseMemFunc$[ebp], OFFSET FLAT:??_9@$B3AE ; `vcall'
取出符号”??_9@$B3AE“的值,存放于变量pVVBaseMemFunc所占内存的前4个字节(DWORD)中。
??_9@$B3AE PROC NEAR ; `vcall', COMDAT
mov eax, DWORD PTR [ecx]
jmp DWORD PTR [eax+4]
??_9@$B3AE ENDP ; `vcall'
_TEXT ENDS
符号”??_9@$B3AE“代表的应该是一个存根函数,这个函数首先根据this指针获得虚函数表的指针,然后将指令再跳转到相应的虚函数的地址。
mov eax, DWORD PTR _pVIBaseMemFunc$[ebp]
mov DWORD PTR _pVIDeriveMemFunc$[ebp], eax
直接将变量pVIBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量_pVIDeriveMemFunc所占内存的前4个字节中。
mov eax, DWORD PTR _pVVBaseMemFunc$[ebp]
mov DWORD PTR _pVVDeriveMemFunc$[ebp], eax
直接将变量pVVBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量pVVDeriveMemFunc所占内存的前4个字节中。
下面的函数调用都大同小异,这里是列出其中的一个: 这里的汇编代码并没有给我们太多新鲜的内容:将对象的首地址(this指针)存放于寄存器ECX中,接着就将指令转到变量_pVIBaseMemFunc所占内存的前4个字节所表示的地址。
到了这里,我们应该对成员函数指针有了进一步的了解。
mov esi, esp
push 10 ; 0000000aH
lea ecx, DWORD PTR _baseObj$[ebp]
call DWORD PTR _pVIBaseMemFunc$[ebp]
cmp esi, esp
call __RTC_CheckEsp
由此可以看出,他们之间的偏移量是12个字节。这12个字节中应该可以包含三个指针,其中的一个指针应该指向函数的地址,那另外两个指针又指向那里呢?在《C++ Common Knowledge: Essential Intermediate Programming》(中文译名:
public:
//ordinary member function
void setValue(int iValue);
//virtual member function
virtual void dumpMe();
virtual void foobar();
protected:
int m_iValue;
};
class Derived:public Base{
public:
//ordinary member function
void setValue(int iValue);
//virtual member function
virtual void dumpMe();
virtual void foobar();
private:
double m_fValue;
};
typedef void (Derived::*PVVDERIVEMEMFUNC)(void);
typedef void (Base::*PVIBASEMEMFUNC)(int);
typedef void (Derived::*PVIDERIVEMEMFUNC)(int);
{
PVIBASEMEMFUNC pVIBaseMemFunc = &Base::setValue;
PVIDERIVEMEMFUNC pVIDeriveMemFunc = static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc);
PVVBASEMEMFUNC pVVBaseMemFunc = &Base::foobar;
PVVDERIVEMEMFUNC pVVDeriveMemFunc = static_cast<PVVDERIVEMEMFUNC>(pVVBaseMemFunc);
Base baseObj;
(baseObj.*pVIBaseMemFunc)(10);
(baseObj.*pVVBaseMemFunc)();
Derived deriveObj;
(deriveObj.*pVIDeriveMemFunc)(20);
(deriveObj.*pVVDeriveMemFunc)();
return 0;
}
_baseObj$ = -60
_pVVDeriveMemFunc$ = -44
_pVVBaseMemFunc$ = -32
_pVIDeriveMemFunc$ = -20
_pVIBaseMemFunc$ = -8
_argc$ = 8
_argv$ = 12
发表评论
-
Berkeley DB 使用经验总结
2012-08-27 14:41 3121作者:陈磊 NoSQL是现在互联网Web2.0时代备受 ... -
嵌入式数据库系统Berkeley DB
2012-08-27 14:37 1563前言 UNIX/LINUX平台下的数据库种类非常多 ... -
C语言中标准输入流、标准输出流、标准错误输出流
2011-06-13 14:32 9331C语言中标准输入流、标准输出流、标准错误输出流 在 ... -
Rsync 实现原理
2011-05-12 20:06 8355Rsync 实现原理 前言 关于rsync的原始文档 ... -
c++简单的虚函数测试
2011-04-27 14:25 1048#include <iostream> u ... -
C++文件行查找
2011-04-26 14:10 1432#include <iostream> # ... -
c++偏特化简单示例
2011-04-13 11:17 2190c++偏特化 // temp1.c ... -
GDB调试精粹及使用实例
2011-03-16 14:06 1163GDB调试精粹及使用实例 一:列文件清单 1. ... -
简单的ini文件解析
2011-02-12 16:36 1649int GetKeyVal(const string s ... -
scanf族函数高级用法
2011-01-25 16:00 2590如何解释 fscanf(fd,&quo ... -
使用scons替代makefile(1)
2011-01-25 11:58 3766早在多年前我刚开始接触linux下的C程序时,经常被makef ... -
使用scons替代makefile(2)
2011-01-25 11:57 3617本篇文章接着上一篇进一步介绍scons的使用方法,主要介绍静态 ... -
使用scons替代makefile(3)
2011-01-25 11:55 4835在上两篇文章中已经简单介绍了用scons编译库文件,可执行程序 ... -
C 支持动态添加测试数据的测试代码
2011-01-13 17:22 1141/下面的定义为了支持可扩增。 //当需要增加一个新的测试用列 ... -
Linux下Makefile的automake生成
2010-12-28 16:55 1134******************helloworld.c* ... -
SCons笔记(详细版)
2010-12-23 16:11 106391. 基本使用 SConstruct文件就功能而言相当于Ma ... -
scons 学习
2010-12-23 11:14 2225scons 学习 作者:Sam(甄峰) sam_code@h ... -
scons随笔
2010-12-22 20:20 4737scons随笔 Scons是新一代的软件构件工具,或者说ma ... -
Scons在linux下的安装和使用
2010-12-21 11:59 3321因为正在用的一个开源软件需要的Developm ... -
排列组合的实现
2010-12-20 12:41 1093简单算法: 从前往后(或者从后往前)每次交换一个位置。当存在 ...
相关推荐
函数指针分为两类:一般(普通)函数指针和类成员函数指针。这两种类型的指针在用法上有所区别,且类成员函数指针涉及到更复杂的面向对象特性。本文将深入探讨它们的区别以及如何进行强制转换。 首先,让我们从一般...
本文将深入探讨如何利用成员函数指针来创建高效的C++委托,并分析其背后的原理和技术细节。 #### 成员函数指针的概念 在C++中,成员函数指针是一种特殊的指针类型,它可以指向类的成员函数。这些成员函数可以是...
本文将深入解析一般函数指针和类的成员函数指针。 1. **一般函数指针** 一般函数指针是指向具有固定参数列表和返回值类型的一般函数的指针。声明函数指针时,需要指定函数的参数类型、顺序和返回值。例如,`int (*...
本主题将深入探讨易语言中简单类函数指针的应用及其相关知识点。 首先,我们要理解什么是函数指针。函数指针本质上是一个存储函数地址的数据类型,它可以被赋值为某个函数的地址,进而可以在运行时调用该函数。在...
// 获取成员函数指针 (obj.*func)(42); // 调用成员函数 ``` 在实际应用中,我们可能会遇到需要根据字符串来动态调用函数的情况。这可以通过建立函数指针的映射表来实现,类似于简单的反射机制。映射表通常是一个...
在C++编程语言中,获取类的成员函数的函数指针是一项重要的技术,它允许我们动态地调用对象...理解并熟练掌握成员函数指针是深入理解C++面向对象编程的关键之一。希望本文的解释和示例代码有助于你理解和应用这个概念。
在易语言中,类的定义通常包含属性(成员变量)和方法(成员函数)。类的实例化过程就是创建一个具体的对象,对象则拥有类定义的属性和可以执行类定义的方法。例如: ```易语言 .类.定义 类名 .属性 属性名, 数据...
下面我们将深入探讨普通函数指针、同一类的成员函数指针以及不同类的成员函数指针的使用。 首先,让我们了解**普通函数指针**。普通函数指针是C++中一种基础的数据类型,可以存储任何无参数或具有特定参数类型的...
本文将深入探讨普通函数指针和成员函数指针的概念及其用法。 首先,让我们看看普通函数指针。普通函数指针是指向不关联任何特定对象的函数的指针。它们的声明方式类似于其他类型的指针,但指针的类型是函数签名。...
理解成员函数指针对于深入掌握C++面向对象编程至关重要。 1. 成员函数指针的声明和使用 成员函数指针的声明格式不同于普通函数指针。假设有一个返回类型为`R`的成员函数,属于类`T`,无参数,那么它的成员函数指针...
成员函数指针用于指向类的成员函数,可以在没有类实例的情况下持有对成员函数的引用。上面的代码片段展示了如何使用成员函数指针: ```cpp class b { public: void print() { cout ; a = 90; cout ; } ...
通过这种方式,我们可以通过全局函数指针调用类的成员函数,从而在Windows Hook中使用类的特性。这是一个巧妙的技巧,但也需要对汇编语言和C++的内存模型有深入的理解。 文章的结尾可能展示了如何创建并使用这样的...
这里的`&MyClass::threadFunction`是成员函数指针,`this`指针用于指定哪个对象的成员函数被调用。 4. **线程同步**: 当主线程需要等待线程完成时,可以使用`join`或`detach`方法。`join`会阻塞直到线程完成,而...
易语言是一种以中文编程为目标的计算机编程语言,它旨在降低编程的难度,让更...为了深入了解,可以查看“取易语言类指针的三个方法源码”中的具体实现,通过阅读和分析代码,可以更好地理解和掌握这些方法的实际应用。
本库利用C++11的新特性,提供了一种优雅的方式来实现这些功能,并允许将回调函数直接绑定到类成员函数。 APIHook技术主要涉及到动态改变API调用的过程。它通常通过在调用API函数之前插入自定义代码来实现,使得原本...
但是,如果你将一个基类指针强制转换为派生类指针,然后尝试访问派生类特有的成员,就会引发未定义行为,因为原始对象可能并不是派生类实例。 函数指针是C++中的另一个强大工具,它可以把函数当作数据处理。你可以...
在Linux系统中,C语言是一种基础且强大的编程语言,它提供了丰富的功能,其中包括函数指针和回调函数的概念。本文将深入探讨这两个主题,并通过实际的示例代码来帮助你理解和应用它们。 首先,我们来理解函数指针。...
在C++编程语言中,一个重要的特性是虚函数(virtual functions),这使得即使通过空对象指针也能调用成员函数,这种行为被称为“空指针调用”或“空对象调用”。本篇将深入探讨这个主题,以及它在C++中的实现原理和...
MFC支持多线程编程,并提供了CWinThread类作为线程的基础,通过继承这个类并重写它的成员函数,我们可以创建自己的线程类。 总的来说,这个压缩包可能包含了一些关于如何使用MFC进行多线程编程,特别是利用函数指针...