天天看點

深入類的成員函數指針

先看這樣一段代碼

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; 

}

結果是:

hello

為何

p=NULL; 

 p->hello();   這樣之後,NULL->hello()也依然有效呢?

我們第一反應一定是覺得類的執行個體的成員函數,不同于成員變量,成員函數全部都存在同一個地方,是以的類的執行個體來調用的時候,一定是調用相同的函數指針。(想想也是有道理的,成員函數都是一樣的功能,根本不需要執行個體化多個)

于是我們把代碼改成這樣

test(int i){ m_i=i;} 

test(){} 

void hello() 

printf("hello/n"); 

int m_i; 

}; 

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

 test q;

 HELLO_FUNC phello_fun=&test::hello;

 printf("%p/n",phello_fun);

 phello_fun=&test::hello;

 phello_fun=p->hello;

 phello_fun=q.hello;

結果是:

00401005

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) 操作符"::*"用來聲明一個類成員函數指針,例如:

    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

    (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   

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{

private:

    double m_fValue;

    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

繼續閱讀