• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            chaosuper85

            C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
              118 Posts :: 0 Stories :: 3 Comments :: 0 Trackbacks

                C++虛函數(shù)探索筆記(1)——虛函數(shù)的簡單示例分析

                關(guān)注問題:

                虛函數(shù)的作用

                虛函數(shù)的實(shí)現(xiàn)原理

                虛函數(shù)表在對象布局里的位置

                虛函數(shù)的類的sizeof

                純虛函數(shù)的作用

                多級繼承時的虛函數(shù)表內(nèi)容

                虛函數(shù)如何執(zhí)行父類代碼

                多繼承時的虛函數(shù)表定位,以及對象布局

                虛析構(gòu)函數(shù)的作用

                虛函數(shù)在QT的信號與槽中的應(yīng)用

                虛函數(shù)與inline修飾符,static修飾符

                啰嗦兩句

                虛函數(shù)在C++里的作用是在是非常非常的大,很多講述C++的文章都會講到它

            ,要用好C++,就一定要學(xué)好虛函數(shù)。網(wǎng)絡(luò)上可以google到很多很多關(guān)于它的文章

            ,這一次的學(xué)習(xí),我不準(zhǔn)備去只是簡單的閱讀了解那些文章,而是希望通過編寫

            一些測試代碼,來對虛函數(shù)的一些實(shí)現(xiàn)機(jī)制,以及C++對象布局做一下探索。

                虛函數(shù)的簡單示例 !

                虛函數(shù)常常出現(xiàn)在一些抽象接口類定義里,當(dāng)然,還有一個更常見的“特例

            ”,那就是虛析構(gòu)函數(shù),后面會提到這個。

                下面是一段關(guān)于虛函數(shù)的簡單代碼,演示了使用基類接口操作對象時的效果


             //Source filename: Win32Con.cpp
            #include <iostream>
            using namespace std;
            class parent1
            {
            public:
                virtual int fun1()=0;
            };

            class child1:public parent1
            {
            public:
                virtual int fun1()
                {
                    cout<<"child1::fun1()"<<endl;
                    return 0;
                }
            };

            class child2:public parent1
            {
            public:
                virtual int fun1()
                {
                    cout<<"child2::fun1()"<<endl;
                    return 0;
                }
            };

            void test_func1(parent1 *pp)
            {
                pp->fun1();
            }

            int main(int argc, char* argv[])
            {
                child1 co1;
                child2 co2;
                test_func1(&co1);
                test_func1(&co2);
                return 0;
            }

             


                在上面的代碼里,類parent1是一個只具有純虛函數(shù)的接口類,這個類不能被

            實(shí)例化,它唯一的用途就是抽象一些特定的接口函數(shù),當(dāng)然,在這里這個接口函

            數(shù)就是純虛函數(shù) parent1::fun1()。

                而類child1和child2則是兩個從parent1繼承的類,我們要使用它定義具體的

            類實(shí)例,所以它實(shí)現(xiàn)了由parent1繼承得來的fun1接口,并且各自的實(shí)現(xiàn)是不同的

                函數(shù) test_func1 的參數(shù)是一個parent1類型的指針,它所要完成的功能就是

            調(diào)用這個parent1對象的fun1()函數(shù)。

                讓我們編譯運(yùn)行一下上面的代碼,可以看到下面的輸出

                child1::fun1()

                child2::fun1()

                很顯然,在兩次調(diào)用test_func1函數(shù)的時候,雖然傳入的參數(shù)都是一個

            parent1的指針,但是卻都分別執(zhí)行了child1和child2各自的fun1函數(shù)!這就是

            C++里類的多態(tài)。然而,這一切是怎么發(fā)生的呢?test_func1函數(shù)怎么會知道應(yīng)該

            調(diào)用哪個函數(shù)的呢?我不準(zhǔn)備像其他人一樣畫若干圖來說明,我準(zhǔn)備用具體某個

            編譯器產(chǎn)生的對象布局以及相應(yīng)的匯編代碼來說明這個過程(這個編譯器是

            vs2008里的vc9)。

                我們先打開一個VS2008命令提示窗口,改變目錄到上面的代碼Win32Con.cpp

            所在目錄,輸入下面的命令:

                cl  win32con.cpp  /d1reportSingleClassLayoutchild

                上面的命令可以編譯win32con.cpp源碼,同時生成里面類名包含child 的類

            的對象布局(layout)

                注意:d1reportSingleClassLayout和后面的child是相連的!

                輸入上面的命令后看到的對象布局如下,紅色字為我添加的注釋
             class child1    size(4): 子類child1的對象布局,只包含一個vfptr,大小為

            4字節(jié)
                    +---
                    | +--- (base class parent1) 這是被嵌套的父類parent1的對象布局
             0      | | {vfptr}
                    | +---
                    +---
            這是child1的vfptr所指的虛函數(shù)表的布局,只包含一個函數(shù)的地址,就是child1

            的fun1函數(shù)
            child1::$vftable@:
                    | &child1_meta
                    |  0
             0      | &child1::fun1

            child1::fun1 this adjustor: 0

            class child2    size(4): 子類child2的對象布局,只包含一個vfptr,大小為4

            字節(jié)
                    +---
                    | +--- (base class parent1) 這是被嵌套的父類parent1的對象布局
             0      | | {vfptr}
                    | +---
                    +---
            這是child2的vfptr所指的虛函數(shù)表的布局,只包含一個函數(shù)的地址,就是child2

            的fun1函數(shù)
            child2::$vftable@:
                    | &child2_meta
                    |  0
             0      | &child2::fun1

            child2::fun1 this adjustor: 0

             


                從上面的對象布局可以知道:

                每個子對象都有一個隱藏的成員變量vfptr(你當(dāng)然不能用這個名字訪問到它

            ),它的值是指向該子對象的虛函數(shù)表,而虛函數(shù)表里填寫的函數(shù)地址是該子對

            象的fun1函數(shù)地址。

                對一個包含有虛函數(shù)的類做sizeof操作的時候,除了能直接看到的成員變量,還得增加4字節(jié)(在32位機(jī)器上),就是vfptr這個指針的大小。

                所以當(dāng)test_func1進(jìn)行pp->fun1()調(diào)用的時候,會首先取出pp所指的內(nèi)存地址并按照parent1的內(nèi)存布局,獲取到vfptr指針(由于pp在兩次調(diào)用中分別指向co1和co2所以這里取得的實(shí)際上是co1的vfptr和co2的vfptr),然后從vfptr所指的虛函數(shù)表第一項(xiàng)(現(xiàn)在也只有 1 項(xiàng))取出作為將要調(diào)用的函數(shù),由于co1和co2在各自的虛函數(shù)表里填寫了各自的fun1的地址,于是pp->fun1()最終就調(diào)用到了co1和co2各自的fun1,輸出自然也就不同了。

                讓我們看看test_func1的反匯編代碼:
             void test_func1(parent1 *pp)
            {
            001C1530  push        ebp
            001C1531  mov         ebp,esp
            001C1533  sub         esp,0C0h
            001C1539  push        ebx
            001C153A  push        esi
            001C153B  push        edi
            001C153C  lea         edi,[ebp-0C0h]
            001C1542  mov         ecx,30h
            001C1547  mov         eax,0CCCCCCCCh
            001C154C  rep stos    dword ptr es:[edi]
                pp->fun1();
            001C154E  mov         eax,dword ptr [pp] //取得pp的值放到eax,即對象的地址
            //取得對象的vfptr地址放到edx(因?yàn)関fptr在對象布局里拍在第一)
            001C1551  mov         edx,dword ptr [eax]
            001C1553  mov         esi,esp
            001C1555  mov         ecx,dword ptr [pp]
            001C1558  mov         eax,dword ptr [edx] //取出vfptr的第一個虛函數(shù)的地址到eax
            001C155A  call        eax //調(diào)用虛函數(shù),即fun1()

                至此,應(yīng)該比較清楚虛函數(shù)機(jī)制的基本實(shí)現(xiàn)了。然而,也許你還會有這些問題:

                虛函數(shù)表是每個子對象都有的么?

                虛函數(shù)是存在一個表里的,表的數(shù)據(jù)結(jié)構(gòu)是怎樣的,如何定位表里哪個才是我們要調(diào)用的虛函數(shù)?

                略作變化

                讓我們對前面的代碼做以下修改:

                定義一個普通類

                修改parent類,在fun1前增加虛函數(shù)fun2

                在child1里和child2里編寫fun2的具體實(shí)現(xiàn),一個在fun1之前編寫,另外一個在之后編寫修改后的編碼大致如下:

             class parent1
            {
            public:
                virtual int fun2()=0;
                virtual int fun1()=0;
            };

            class child
            {
                int a;
            };

            class child1:public parent1
            {
            public:

                virtual int fun1()
                {
                    cout<<"child1::fun1()"<<endl;
                    return 0;
                }
                virtual int fun2()
                {
                    cout<<"child1::fun2()"<<endl;
                    return 0;
                }
            };

                然后我們再使用cl命令以及/d1reportSingleClassLayout選項(xiàng)輸出相關(guān)的類對象布局情況:

             class child     size(4):  //在普通類child里,看不到vfptr的身影!
                    +---
             0      | a
                    +---

            class child1    size(4):    //child1的對象布局,和之前沒有變化!
                    +---
                    | +--- (base class parent1)
             0      | | {vfptr}
                    | +---
                    +---
            //child1的虛函數(shù)表多了fun2,并且兩個虛函數(shù)在表里的順序相同于在parent類里聲明的順序
            child1::$vftable@:
                    | &child1_meta
                    |  0
             0      | &child1::fun2
             1      | &child1::fun1

            child1::fun1 this adjustor: 0
            child1::fun2 this adjustor: 0

                結(jié)論很明顯:

                虛函數(shù)表指針vfptr只在類里有虛擬函數(shù)的時候才會存在當(dāng)有多個虛函數(shù)的時候,虛函數(shù)在虛函數(shù)表里的順序由父類里虛函數(shù)的定義順序決定并且我們還可以觀察到:

                這個vfptr指針會放在類的起始處(這是必須的,vfptr在父類和子類的對象布局上必須一致!)

                虛函數(shù)表是以一個NULL指針標(biāo)識結(jié)束讓我們對這次簡單的示例代碼測試來做個小小總結(jié):

                有虛函數(shù)的類,一定會有一個虛函數(shù)表指針vfptr這個vfptr指針會放在類的起始處虛函數(shù)表里會按基類聲明虛函數(shù)的順序在vfptr里存放函數(shù)地址虛函數(shù)表里存放的是函數(shù)地址是具體子類的實(shí)現(xiàn)函數(shù)的地址調(diào)用虛函數(shù)的時候,是從vfptr所指的函數(shù)表里獲取到函數(shù)地址,然后才調(diào)用具體的代碼。

             


            posted on 2009-08-05 17:42 chaosuper 閱讀(348) 評論(1)  編輯 收藏 引用

            Feedback

            # re: C++中虛函數(shù)深度探索 一 (轉(zhuǎn)載)[未登錄] 2009-08-06 23:15 ttylikl
            呵呵,老兄,留個原文鏈接嘛!  回復(fù)  更多評論
              


            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            国产成人精品白浆久久69| 亚洲国产成人乱码精品女人久久久不卡| 欧美日韩精品久久久免费观看| 99久久www免费人成精品| 伊人久久大香线蕉av不卡| 亚洲AV无码1区2区久久| 久久综合中文字幕| 久久久久国产一级毛片高清板| 天天综合久久一二三区| 精品久久久久久无码专区| 99久久国产热无码精品免费久久久久| 久久精品国产色蜜蜜麻豆| 亚洲va久久久噜噜噜久久狠狠| 久久久久久免费一区二区三区| 久久综合九色欧美综合狠狠| 亚洲AV日韩精品久久久久久| 狠狠色综合久久久久尤物| 国内精品久久久久影院薰衣草 | 久久久久亚洲精品无码网址| 久久WWW免费人成一看片| 久久国产福利免费| 久久国产高潮流白浆免费观看| 久久97久久97精品免视看| 国产精品99久久99久久久| 一本综合久久国产二区| 久久本道综合久久伊人| 成人资源影音先锋久久资源网| 亚洲伊人久久成综合人影院| 久久久精品午夜免费不卡| 久久婷婷五月综合97色一本一本| 日本国产精品久久| 久久精品成人一区二区三区| 久久精品国产亚洲综合色| 久久精品九九亚洲精品| 囯产精品久久久久久久久蜜桃| 久久亚洲中文字幕精品一区| 国产三级精品久久| 久久精品国产99久久丝袜| 国产激情久久久久影院老熟女| 久久国产乱子伦精品免费强| 97超级碰碰碰久久久久|