类成员函数指针

From:http://blog.youkuaiyun.com/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个参数是线程函数的指针,为何这里报错呢?

 

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

 

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

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

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

1) 操作符"::*"用来声明一个类成员函数指针,例如:
    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 (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 “,我又找到了这样的汇编代码: 由此可以看出,对于虚函数,即使是用过成员函数指针间接调用,仍然具有和直接调用一样的特性。

 

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

 

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

 

    _TEXT    SEGMENT
    ??_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指针获得虚函数表的指针,然后将指令再跳转到相应的虚函数的地址。

 

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

 

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

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

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

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

    ; (baseObj.*pVIBaseMemFunc)(10);
    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》(中文译名:

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

class Base {
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 (Base::*PVVBASEMEMFUNC)(void);
    typedef void (Derived::*PVVDERIVEMEMFUNC)(void);
    typedef void (Base::*PVIBASEMEMFUNC)(int);
    typedef void (Derived::*PVIDERIVEMEMFUNC)(int);

 

int _tmain(int argc, _TCHAR* argv[])
{
    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;
}

 

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

 


本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/eroswang/archive/2009/05/06/4153356.aspx

 

最后关于使用UNION跳过C++强类型转换的说明请转向 http://tech.ddvip.com/2008-03/120660633743744.html

<think>首先,用户的问题是关于“C++类成员函数指针使用方法示例”。回顾之前的对话历史:-用户最初问的是关于“名访问静态成员变量”,我详细解释了声明、初始化、访问方法、注意事项和示例。-现在,用户说:“参考站内引用:”,并提供了几个引用,涉及类成员函数指针的应用和示例:-引用[1]:展示了声明类成员函数指针的示例。-引用[2]:讨论了C++中类成员函数指针的使用区别。-引用[3]:展示了指向静态成员函数的指针使用,指出它与普通函数指针似。-引用[4]:讨论了如何将的非静态成员函数用作回调函数。-用户明确表示:“我想了解类成员函数指针的用法或解决相关问题”。系统指令:-使用LaTeX:行内用`$...$`,独立公式用`$$...$$`。-中文回答。-生成相关问题。-引用段落末尾自然地添加引用标识,例如[^1]。-回答结构清晰,逐步解决问题,保证真实可靠。-在末尾添加相关问题。从引用中提取关键点:-类成员函数指针用于指向的成员函数。-非静态成员函数指针需要额外的对象实例来调用。-静态成员函数指针可以像普通函数指针一样使用。-有陷阱,如"referencetonon-staticmemberfunctionmustbecalled"错误。我的回答需要:1.**结构清晰**:逐步解释使用方法。2.**提供示例**:包括代码示例。3.**区分静态和非静态**:因为引用[3]强调了区别。4.**添加引用**:自然地引用相关段落,基于用户提供的引用。5.**生成相关问题**:在回答末尾。大纲:-**引言**:简要介绍类成员函数指针。-**声明和初始化**:解释如何声明类成员函数指针,包括语法。-**使用示例**:提供代码示例:-非静态成员函数的指针使用。-静态成员函数的指针使用(因为它更简单)。-如何使用回调函数的示例(基于引用[4])。-**注意事项**:比如调用非静态成员函数指针时需要一个对象实例。-**结论**:总结关键点。-**相关问题**:生成2-3个相关问题。确保使用LaTeX:-在需要处添加数学表达式,但由于主题与数学无关,可能只用于强调变量或函数名。-例如,解释指针型时,可以用`$type(*ptr)(params)$`。参考用户提供的引用:-在回答中,自然地插入引用标识。-例如,在解释时引用[^1],[^2],[^3],[^4]。具体步骤:1.**介绍类成员函数指针**:解释它用于指向的成员函数。2.**语法声明**:使用用户引用[1]的代码作为基础。-示例:`void(MyClass::*ptr)(int)=&MyClass::MyFunction;`3.**非静态成员函数指针**:-调用时需要对象实例。-示例代码展示。4.**静态成员函数指针**:它像普通函数指针。-基于引用[3]的代码。5.**回调函数应用**:讨论如何用于回调(来自引用[4])。6.**注意事项**:-编译错误"referencetonon-staticmemberfunctionmustbecalled"(引用[2])。-确保权限控制(私有成员)。7.**代码示例**:用代码块展示。-使用C++代码。8.**生成相关问题**:-例如:"如何避免类成员函数指针的常见错误?"、"静态vs非静态成员函数指针的区别?"、"在实际项目中如何使用类成员函数指针?"完整响应结构:-**标题**:C++类成员函数指针的使用方法及示例-**正文**:分步骤解释。-**代码示例**:用Markdown代码块。-**注意事项**:列出关键点。-**相关问题**:在结尾。保持中文回答。</think> ### C++ 类成员函数指针的使用方法及示例 类成员函数指针是C++中用于指向类成员函数的指针型。它分为**非静态成员函数指针**和**静态成员函数指针**,用法略有不同。以下我将逐步解释其使用方法、提供代码示例,并讨论注意事项。本内容基于C++标准,参考您提供的引用材料[^1][^2][^3][^4]。 --- ### 🔑 1. 声明和初始化类成员函数指针 在C++中,声明类成员函数指针需要使用特定语法:`ReturnType (ClassName::*ptr)(Params)`,其中 `ptr` 是指针名, `ReturnType` 是函数返回型, `Params` 是函数参数表。这与普通函数指针不同,因为它需要名限定作用域。 - **非静态成员函数指针**:需要绑定到对象实例上调用。声明时指定名,如 `void (MyClass::*ptr)(int)`。 - **静态成员函数指针**:与普通函数指针似,因为静态成员函数不依赖对象实例。声明时为 `ReturnType (*ptr)(Params)`,不需要名限定。 参考引用[^1]的代码: ```cpp class MyClass { public: void MyFunction(int x) { /* 非静态成员函数 */ } static void StaticFunction(int x) { /* 静态成员函数 */ } }; int main() { void (MyClass::*nonStaticPtr)(int) = &MyClass::MyFunction; // 声明非静态成员函数指针 void (*staticPtr)(int) = &MyClass::StaticFunction; // 声明静态成员函数指针 return 0; } ``` #### 要点: - 初始化时,使用 `&ClassName::FunctionName` 获取函数地址。静态成员函数指针可直接赋值,不需要名作用域。 - 编译错误常见于非静态指针的错误初始化,如未指定对象调用时会出现 "reference to non-static member function must be called"[^2]。 --- ### 🖥 2. 使用示例:非静态成员函数指针 调用非静态成员函数指针时,必须先有一个对象实例,并通过对象指针或对象引用结合特殊语法调用。格式为:`(object.*ptr)(args)` 或 `(objectPtr->*ptr)(args)`。 #### 完整代码示例 ```cpp #include <iostream> class Calculator { public: int add(int a, int b) { return a + b; } // 非静态成员函数 }; int main() { Calculator calc; // 创建对象实例 Calculator* calcPtr = &calc; // 对象指针 // 声明并初始化指针 int (Calculator::*funcPtr)(int, int) = &Calculator::add; // 调用:通过对象实例 int result1 = (calc.*funcPtr)(2, 3); // 输出: 5 // 调用:通过对象指针 int result2 = (calcPtr->*funcPtr)(4, 5); // 输出: 9 std::cout << "result1: " << result1 << ", result2: " << result2 << std::endl; return 0; } ``` #### 解释: - 这里 `funcPtr` 指向 `Calculator::add`,调用时必须使用 `.*` 或 `->*` 运算符与对象关联。 - 如果没有对象实例,试图调用 `funcPtr` 会导致编译错误,如引用[^2]所述。 --- ### 🖥 3. 使用示例:静态成员函数指针 静态成员函数指针似于普通函数指针,因为它不依赖对象实例。直接使用指针名调用即可:`ptr(args)`。 #### 完整代码示例(参考引用[^3]) ```cpp #include <iostream> class Math { public: static int multiply(int a, int b) { return a * b; } // 静态成员函数 }; int main() { // 声明并初始化指针 int (*funcPtr)(int, int) = &Math::multiply; // 直接调用,无需对象 int result = funcPtr(3, 4); // 等同于 Math::multiply(3, 4) std::cout << "乘法结果: " << result << std::endl; // 输出: 12 return 0; } ``` #### 解释: - 静态成员函数指针 `funcPtr` 的声明和调用方式与普通函数指针一致 ($funcPtr \equiv \text{普通函数指针}$)。 - 这指针常用于回调函数场景,因为不需要管理对象生命周期,适合解耦设计[^3]。 --- ### 🔄 4. 使用示例:类成员函数指针作为回调函数 非静态成员函数指针需要特殊处理才能用于回调,因为标准函数指针型不能直接绑定非静态成员。解决方法是使用**包装器**或**lambda函数**。 #### 完整代码示例(基于引用[^4]) ```cpp #include <iostream> #include <functional> // 用于std::function和std::bind class EventHandler { public: void onEvent(int data) { std::cout << "事件处理: " << data << std::endl; } }; // 回调函数型定义 using Callback = std::function<void(int)>; void triggerEvent(Callback callback, int value) { callback(value); // 调用回调 } int main() { EventHandler handler; // 方法1: 使用std::bind包装非静态成员函数指针 Callback callback1 = std::bind(&EventHandler::onEvent, &handler, std::placeholders::_1); // 方法2: 使用lambda表达式捕获对象 Callback callback2 = [&handler](int data) { handler.onEvent(data); }; triggerEvent(callback1, 100); // 输出: 事件处理: 100 triggerEvent(callback2, 200); // 输出: 事件处理: 200 return 0; } ``` #### 解释: - `std::bind` 或 `lambda` 可以将对象实例与成员函数指针绑定,转换为可调用型 ($\text{Callback}$)。 - 这种方法避免了直接传递非静态指针的错误,适合事件驱动或接口编程[^4]。 --- ### ⚠️ 5. 注意事项和常见问题 1. **非静态指针调用要求**: 必须提供一个有效的对象实例,否则导致编译错误 "reference to non-static member function must be called"[^2]。 2. **访问权限**: 指针只能访问公有成员函数。私有函数无法通过指针外部访问 ($\text{ClassName::*ptr}$ 受权限约束)。 3. **性能影响**: 调用非静态指针可能引入间接开销,但在现代编译器中优化良好。 4. **静态 vs 非静态区别**: - 非静态指针:需要对象,指针型为 `ReturnType (ClassName::*)(Params)`。 - 静态指针:无需对象,指针型为 `ReturnType (*)(Params)`[^3]。 5. **多线程风险**: 在多线程环境使用对象指针时,需确保对象生命周期安全。 6. **编译器兼容性**: 所有现代C++编译器(GCC、Clang、MSVC)均支持,但初始化语法需准确。 --- ### 💎 总结 - **类成员函数指针**提供灵活指向类成员函数的能力,但必须区分静态与非静态型。 - 使用时: - 非静态指针:用 `(object.*ptr)` 语法调用。 - 静态指针:像普通函数指针调用。 - 回调场景:使用 `std::bind` 或 `lambda` 包装。 - 实际应用包括事件处理、插件系统等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值