一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

類(lèi)的成員函數(shù)指針(比較深入)

 心不留意外塵 2016-04-22
http://andylin02./blog/469051

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

個(gè)人感覺(jué)對(duì)于類(lèi)的成員函數(shù)指針這塊講解的比較深入詳細(xì)

推薦閱讀

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

 

先看這樣一段代碼

 

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(); 
}

 

結(jié)果是:

hello

hello

 

為何

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

 

我們第一反應(yīng)一定是覺(jué)得類(lèi)的實(shí)例的成員函數(shù),不同于成員變量,成員函數(shù)全部都存在同一個(gè)地方,所以的類(lèi)的實(shí)例來(lái)調(diào)用的時(shí)候,一定是調(diào)用相同的函數(shù)指針。(想想也是有道理的,成員函數(shù)都是一樣的功能,根本不需要實(shí)例化多個(gè))

于是我們把代碼改成這樣

 

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(); 
}

 

結(jié)果是:

hello
00401005
00401005
00401005
00401005
hello
Press any key to continue

 

也就是說(shuō)不管是&test::hello,還是p->hello,或者q.hello,甚至于NULL->hello.

調(diào)用的地址都是0x00401005,也基本印證了我們的猜想。

 

事情到這里算是完了么?沒(méi)有。

有人問(wèn)道這樣一段代碼:

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); 
}
 
行報(bào)錯(cuò):invalid conversion from 'void (*)(void*)' to 'void* (*)(void*)'

pthread_create我就不解釋了,第3個(gè)參數(shù)是線程函數(shù)的指針,為何這里報(bào)錯(cuò)呢?

 

說(shuō)明普通的類(lèi)成員函數(shù)的指針(如果它有函數(shù)指針的話),不同于一般的函數(shù)指針。

 

看看下面這篇文章關(guān)于這個(gè)問(wèn)題的分析:

前言:在CSDN論壇經(jīng)常會(huì)看到一些關(guān)于類(lèi)成員函數(shù)指針的問(wèn)題,起初我并不在意,以為成員函數(shù)指針和普通的函數(shù)指針是一樣的,沒(méi)有什么太多需要討論的。當(dāng)我找來(lái)相關(guān)書(shū)籍查閱了一番以后,突然意識(shí)到我以前對(duì)成員函數(shù)指針的理解太過(guò)于幼稚和膚淺了,它即不像我以前認(rèn)為的那樣簡(jiǎn)單,它也不像我以前認(rèn)為的那樣"默默無(wú)聞"。強(qiáng)烈的求知欲促使我對(duì)成員函數(shù)進(jìn)行進(jìn)一步的學(xué)習(xí)并有了這篇文章。

一。理論篇
在進(jìn)行深入學(xué)習(xí)和分析之前,還是先看看書(shū)中是怎么介紹成員函數(shù)的。總結(jié)一下類(lèi)成員函數(shù)指針的內(nèi)容,應(yīng)該包含以下幾個(gè)知識(shí)點(diǎn):
1
。成員函數(shù)指針并不是普通的函數(shù)指針。
2
。編譯器提供了幾個(gè)新的操作符來(lái)支持成員函數(shù)指針操作:

1) 操作符"::*"用來(lái)聲明一個(gè)類(lèi)成員函數(shù)指針,例如:
    typedef void (Base::*PVVBASEMEMFUNC)(void);        //Base is a class
2) 
操作符"->*"用來(lái)通過(guò)對(duì)象指針調(diào)用類(lèi)成員函數(shù)指針,例如:
    //pBase is a Base pointer and well initialized
    //pVIBaseMemFunc is a member function pointer and well initialized

    (pBase->*pVIBaseMemFunc)();

3) 操作符".*"用來(lái)通過(guò)對(duì)象調(diào)用類(lèi)成員函數(shù)指針,例如:
    
//baseObj is a Base object
    //pVIBaseMemFunc is a member function pointer and well initialized

    (baseObj.*pVIBaseMemFunc)(); 


3
。成員函數(shù)指針是強(qiáng)類(lèi)型的。

    typedef void (Base::*PVVBASEMEMFUNC)(void);
    typedef 
void (Derived::*PVVDERIVEMEMFUNC)(void);
PVVBASEMEMFUNC
PVVDERIVEMEMFUNC是兩個(gè)不同類(lèi)型的成員函數(shù)指針類(lèi)型。


4
。由于成員函數(shù)指針并不是真真意義上的指針,所以成員函數(shù)指針的轉(zhuǎn)化就受限制。具體的轉(zhuǎn)化細(xì)節(jié)依賴于不同的編譯器,甚至是同一個(gè)編譯器的不同版本。不過(guò),處于同一個(gè)繼承鏈中的不同類(lèi)之間override的不同函數(shù)和虛函數(shù)還是可以轉(zhuǎn)化的。

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


二。實(shí)踐篇
有了上面的理論知識(shí),我們對(duì)類(lèi)成員函數(shù)指針有了大概的了解,但是我們對(duì)成員函數(shù)指針還存在太多的疑惑。既然說(shuō)成員函數(shù)指針不是指針,那它到底是什么東東 編譯器為什么要限制成員函數(shù)指針轉(zhuǎn)化?老辦法,我們還是分析匯編代碼揭示其中的秘密。首先,我寫(xiě)了這樣兩個(gè)具有繼承關(guān)系的類(lèi): 
接著,我又定義了一些成員函數(shù)指針類(lèi)型: 
最后,在main函數(shù)寫(xiě)了一些測(cè)試代碼: 
成功編譯后生成匯編代碼。老規(guī)矩,在分析匯編代碼的過(guò)程中還是只分析對(duì)解決問(wèn)題有意義的匯編代碼,其他的就暫時(shí)忽略。
1
。成員函數(shù)指針不是指針。從代碼看出,在main函數(shù)的調(diào)用棧(calling stack)中首先依次壓入四個(gè)成員函數(shù)指針,如果它們是普通指針的話,它們之間的偏移量應(yīng)該是4個(gè)字節(jié),可是實(shí)際的情況卻是這樣的:

 

 ”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
。成員函數(shù)指針的轉(zhuǎn)化。本文所采用的代碼是想比較普通成員函數(shù)指針和虛函數(shù)指針在轉(zhuǎn)化的過(guò)程中存在那些差異: 
對(duì)于符號(hào)”??_9@$B3AE“,我又找到了這樣的匯編代碼: 由此可以看出,對(duì)于虛函數(shù),即使是用過(guò)成員函數(shù)指針間接調(diào)用,仍然具有和直接調(diào)用一樣的特性。

    ; PVIBASEMEMFUNC pVIBaseMemFunc = &Base::setValue;
    mov    DWORD PTR _pVIBaseMemFunc$[ebp], OFFSET FLAT:?setValue@Base@@QAEXH@Z ; 
    
取出Base::setValue函數(shù)的地址,存放于變量pVIBaseMemFunc所占內(nèi)存的前4個(gè)字節(jié)(DWORD)中。

 

; PVVBASEMEMFUNC      pVVBaseMemFunc   = &Base::foobar;
mov    DWORD PTR _pVVBaseMemFunc$[ebp], OFFSET FLAT:??_9@$B3AE ; `vcall'
取出符號(hào)”??_9@$B3AE“的值,存放于變量pVVBaseMemFunc所占內(nèi)存的前4個(gè)字節(jié)(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
符號(hào)”??_9@$B3AE“代表的應(yīng)該是一個(gè)存根函數(shù),這個(gè)函數(shù)首先根據(jù)this指針獲得虛函數(shù)表的指針,然后將指令再跳轉(zhuǎn)到相應(yīng)的虛函數(shù)的地址。

 

    ; PVIDERIVEMEMFUNC pVIDeriveMemFunc = static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc);
    mov    eax, DWORD PTR _pVIBaseMemFunc$[ebp]
    mov    DWORD PTR _pVIDeriveMemFunc$[ebp], eax
直接將變量pVIBaseMemFunc所占內(nèi)存的前4個(gè)字節(jié)(DWORD)的值付給了變量_pVIDeriveMemFunc所占內(nèi)存的前4個(gè)字節(jié)中。

 

    ; PVVDERIVEMEMFUNC    pVVDeriveMemFunc = static_cast<PVVDERIVEMEMFUNC>(pVVBaseMemFunc);
    mov    eax, DWORD PTR _pVVBaseMemFunc$[ebp]
    mov    DWORD PTR _pVVDeriveMemFunc$[ebp], eax
直接將變量pVVBaseMemFunc所占內(nèi)存的前4個(gè)字節(jié)(DWORD)的值付給了變量pVVDeriveMemFunc所占內(nèi)存的前4個(gè)字節(jié)中。

由此可以看出,基類(lèi)的成員函數(shù)指針轉(zhuǎn)化到相應(yīng)的派生類(lèi)的成員函數(shù)指針,值保持不變。當(dāng)然這里的例子繼承關(guān)系相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,如果存在多繼承和虛繼承的情況下,結(jié)果可能會(huì)復(fù)雜的多。

3。函數(shù)調(diào)用
下面的函數(shù)調(diào)用都大同小異,這里是列出其中的一個(gè): 這里的匯編代碼并沒(méi)有給我們太多新鮮的內(nèi)容:將對(duì)象的首地址(this指針)存放于寄存器ECX中,接著就將指令轉(zhuǎn)到變量_pVIBaseMemFunc所占內(nèi)存的前4個(gè)字節(jié)所表示的地址。

到了這里,我們應(yīng)該對(duì)成員函數(shù)指針有了進(jìn)一步的了解。

    ; (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個(gè)字節(jié)。這12個(gè)字節(jié)中應(yīng)該可以包含三個(gè)指針,其中的一個(gè)指針應(yīng)該指向函數(shù)的地址,那另外兩個(gè)指針又指向那里呢?在《C++ Common Knowledge: Essential Intermediate Programming(中文譯名:

C++必知必會(huì))這本書(shū)的第16章對(duì)這部分的內(nèi)容做了說(shuō)明,這個(gè)12個(gè)字節(jié)的偏移量正好印證了書(shū)中的內(nèi)容:

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

 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多

    91精品国产综合久久不卡| 大香蕉大香蕉手机在线视频| 国产超薄黑色肉色丝袜| 久草精品视频精品视频精品| 国产精欧美一区二区三区久久| 色无极东京热男人的天堂| 免费福利午夜在线观看| 中文字幕一区二区熟女| 国产小青蛙全集免费看| 亚洲国产一区精品一区二区三区色| 亚洲av在线视频一区| 中文字幕不卡欧美在线| 亚洲超碰成人天堂涩涩| 手机在线观看亚洲中文字幕| 国产91色综合久久高清| 99热在线精品视频观看| 国产成人精品一区二区在线看| 一区二区三区18禁看| 亚洲精品一区三区三区| 成人午夜激情免费在线| 亚洲熟女国产熟女二区三区| 国产日韩综合一区在线观看| 国产原创中文av在线播放 | 亚洲专区中文字幕视频| 欧美一区二区口爆吞精| 好吊色欧美一区二区三区顽频| 久久精品中文扫妇内射| 欧美加勒比一区二区三区| 国产午夜福利一区二区| 欧美小黄片在线一级观看| 欧美一区二区三区十区| 高清在线精品一区二区| 国产一区二区三区不卡| 欧美日韩国产成人高潮| 一区二区三区在线不卡免费| 欧美自拍偷自拍亚洲精品| 国产精品不卡高清在线观看| 亚洲精品中文字幕熟女| 精品人妻一区二区三区四区久久| 91人妻人人精品人人爽| 五月天婷亚洲天婷综合网|