• <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>

            yehao's Blog

            為什么說虛函數(shù)的效率低

            虛函數(shù)聯(lián)系到多態(tài),多態(tài)聯(lián)系到繼承。所以本文中都是在繼承層次上做文章。沒了繼承,什么都沒得談。

              下面是對C++的虛函數(shù)這玩意兒的理解。

              一, 什么是虛函數(shù)(如果不知道虛函數(shù)為何物,但有急切的想知道,那你就應該從這里開始)

              簡單地說,那些被virtual關(guān)鍵字修飾的成員函數(shù),就是虛函數(shù)。虛函數(shù)的作用,用專業(yè)術(shù)語來解釋就是實現(xiàn)多態(tài)性(Polymorphism),多態(tài)性是將接口與實現(xiàn)進行分離;用形象的語言來解釋就是實現(xiàn)以共同的方法,但因個體差異而采用不同的策略。下面來看一段簡單的代碼

              class A{

              public:

              void print(){ cout<<”This is A”<<endl;}

              };

              class B:public A{

              public:

              void print(){ cout<<”This is B”<<endl;}

              };

              int main(){ //為了在以后便于區(qū)分,我這段main()代碼叫做main1

              A a;

              B b;

              a.print();

              b.print();

              }

              通過class A和class B的print()這個接口,可以看出這兩個class因個體的差異而采用了不同的策略,輸出的結(jié)果也是我們預料中的,分別是This is A和This is B。但這是否真正做到了多態(tài)性呢?No,多態(tài)還有個關(guān)鍵之處就是一切用指向基類的指針或引用來操作對象。那現(xiàn)在就把main()處的代碼改一改。

              int main(){ //main2

              A a;

              B b;

              A* p1=&a;

              A* p2=&b;

              p1->print();

              p2->print();

              }

              運行一下看看結(jié)果,喲呵,驀然回首,結(jié)果卻是兩個This is A。問題來了,p2明明指向的是class B的對象但卻是調(diào)用的class A的print()函數(shù),這不是我們所期望的結(jié)果,那么解決這個問題就需要用到虛函數(shù)

              class A{

              public:

              virtual void print(){ cout<<”This is A”<<endl;} //現(xiàn)在成了虛函數(shù)了

              };

              class B:public A{

              public:

              void print(){ cout<<”This is B”<<endl;} //這里需要在前面加上關(guān)鍵字virtual嗎?

              };

              毫無疑問,class A的成員函數(shù)print()已經(jīng)成了虛函數(shù),那么class B的print()成了虛函數(shù)了嗎?回答是Yes,我們只需在把基類的成員函數(shù)設(shè)為virtual,其派生類的相應的函數(shù)也會自動變?yōu)樘摵瘮?shù)。所以,class B的print()也成了虛函數(shù)。那么對于在派生類的相應函數(shù)前是否需要用virtual關(guān)鍵字修飾,那就是你自己的問題了。

              現(xiàn)在重新運行main2的代碼,這樣輸出的結(jié)果就是This is A和This is B了。

              現(xiàn)在來消化一下,我作個簡單的總結(jié),指向基類的指針在操作它的多態(tài)類對象時,會根據(jù)不同的類對象,調(diào)用其相應的函數(shù),這個函數(shù)就是虛函數(shù)。

              二, 虛函數(shù)是如何做到的(如果你沒有看過《Inside The C++ Object Model》這本書,但又急切想知道,那你就應該從這里開始)

              虛函數(shù)是如何做到因?qū)ο蟮牟煌{(diào)用其相應的函數(shù)的呢?現(xiàn)在我們就來剖析虛函數(shù)。我們先定義兩個類

              class A{ //虛函數(shù)示例代碼

              public:

              virtual void fun(){cout<<1<<endl;}

              virtual void fun2(){cout<<2<<endl;}

              };

              class B:public A{

              public:

              void fun(){cout<<3<<endl;}

              void fun2(){cout<<4<<endl;}

              };

              由于這兩個類中有虛函數(shù)存在,所以編譯器就會為他們兩個分別插入一段你不知道的數(shù)據(jù),并為他們分別創(chuàng)建一個表。那段數(shù)據(jù)叫做vptr指針,指向那個表。那個表叫做vtbl,每個類都有自己的vtbl,vtbl的作用就是保存自己類中虛函數(shù)的地址,我們可以把vtbl形象地看成一個數(shù)組,這個數(shù)組的每個元素存放的就是虛函數(shù)的地址,請看圖

              通過上圖,可以看到這兩個vtbl分別為class A和class B服務。現(xiàn)在有了這個模型之后,我們來分析下面的代碼

              A *p=new A;

              p->fun();

              毫無疑問,調(diào)用了A::fun(),但是A::fun()是如何被調(diào)用的呢?它像普通函數(shù)那樣直接跳轉(zhuǎn)到函數(shù)的代碼處嗎?No,其實是這樣的,首先是取出vptr的值,這個值就是vtbl的地址,再根據(jù)這個值來到vtbl這里,由于調(diào)用的函數(shù)A::fun()是第一個虛函數(shù),所以取出vtbl第一個slot里的值,這個值就是A::fun()的地址了,最后調(diào)用這個函數(shù)。現(xiàn)在我們可以看出來了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里裝著對應類的虛函數(shù)地址,所以這樣虛函數(shù)就可以完成它的任務。

              而對于class A和class B來說,他們的vptr指針存放在何處呢?其實這個指針就放在他們各自的實例對象里。由于class A和class B都沒有數(shù)據(jù)成員,所以他們的實例對象里就只有一個vptr指針。通過上面的分析,現(xiàn)在我們來實作一段代碼,來描述這個帶有虛函數(shù)的類的簡單模型。

              #include<iostream>

              using namespace std;

              //將上面“虛函數(shù)示例代碼”添加在這里

              int main(){

              void (*fun)(A*);

              A *p=new B;

              long lVptrAddr;

              memcpy(&lVptrAddr,p,4);

              memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4);

              fun(p);

              delete p;

              system("pause");

              }

              用VC或Dev-C++編譯運行一下,看看結(jié)果是不是輸出3,如果不是,那么太陽明天肯定是從西邊出來?,F(xiàn)在一步一步開始分析

              void (*fun)(A*); 這段定義了一個函數(shù)指針名字叫做fun,而且有一個A*類型的參數(shù),這個函數(shù)指針待會兒用來保存從vtbl里取出的函數(shù)地址

              A* p=new B; new B是向內(nèi)存(內(nèi)存分5個區(qū):全局名字空間,自由存儲區(qū),寄存器,代碼空間,棧)自由存儲區(qū)申請一個內(nèi)存單元的地址然后隱式地保存在一個指針中.然后把這個地址附值給A類型的指針P.

              .

              long lVptrAddr; 這個long類型的變量待會兒用來保存vptr的值

              memcpy(&lVptrAddr,p,4); 前面說了,他們的實例對象里只有vptr指針,所以我們就放心大膽地把p所指的4bytes內(nèi)存里的東西復制到lVptrAddr中,所以復制出來的4bytes內(nèi)容就是vptr的值,即vtbl的地址

              現(xiàn)在有了vtbl的地址了,那么我們現(xiàn)在就取出vtbl第一個slot里的內(nèi)容

              memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4); 取出vtbl第一個slot里的內(nèi)容,并存放在函數(shù)指針fun里。需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指針,所以我們要把它先轉(zhuǎn)變成指針類型

              fun(p); 這里就調(diào)用了剛才取出的函數(shù)地址里的函數(shù),也就是調(diào)用了B::fun()這個函數(shù),也許你發(fā)現(xiàn)了為什么會有參數(shù)p,其實類成員函數(shù)調(diào)用時,會有個this指針,這個p就是那個this指針,只是在一般的調(diào)用中編譯器自動幫你處理了而已,而在這里則需要自己處理。

              delete p; 釋放由p指向的自由空間;

              system("pause"); 屏幕暫停;

              如果調(diào)用B::fun2()怎么辦?那就取出vtbl的第二個slot里的值就行了

              memcpy(&fun,reinterpret_cast<long*>(lVptrAddr+4),4); 為什么是加4呢?因為一個指針的長度是4bytes,所以加4。或者memcpy(&fun,reinterpret_cast<long*>(lVptrAddr)+1,4); 這更符合數(shù)組的用法,因為lVptrAddr被轉(zhuǎn)成了long*型別,所以+1就是往后移sizeof(long)的長度

              三, 以一段代碼開始

              #include<iostream>

              using namespace std;

              class A{ //虛函數(shù)示例代碼2

              public:

              virtual void fun(){ cout<<"A::fun"<<endl;}

              virtual void fun2(){cout<<"A::fun2"<<endl;}

              };

              class B:public A{

              public:

              void fun(){ cout<<"B::fun"<<endl;}

              void fun2(){ cout<<"B::fun2"<<endl;}

              }; //end//虛函數(shù)示例代碼2

              int main(){

              void (A::*fun)(); //定義一個函數(shù)指針

              A *p=new B;

              fun=&A::fun;

              (p->*fun)();

              fun = &A::fun2;

              (p->*fun)();

              delete p;

              system("pause");

              }

              你能估算出輸出結(jié)果嗎?如果你估算出的結(jié)果是A::fun和A::fun2,呵呵,恭喜恭喜,你中圈套了。其實真正的結(jié)果是B::fun和B::fun2,如果你想不通就接著往下看。給個提示,&A::fun和&A::fun2是真正獲得了虛函數(shù)的地址嗎?

              首先我們回到第二部分,通過段實作代碼,得到一個“通用”的獲得虛函數(shù)地址的方法

              #include<iostream>

              using namespace std;

              //將上面“虛函數(shù)示例代碼2”添加在這里

              void CallVirtualFun(void* pThis,int index=0){

              void (*funptr)(void*);

              long lVptrAddr;

              memcpy(&lVptrAddr,pThis,4);

              memcpy(&funptr,reinterpret_cast<long*>(lVptrAddr)+index,4);

              funptr(pThis); //調(diào)用

              }

              int main(){

              A* p=new B;

              CallVirtualFun(p); //調(diào)用虛函數(shù)p->fun()

              CallVirtualFun(p,1);//調(diào)用虛函數(shù)p->fun2()

              system("pause");

              }

              現(xiàn)在我們擁有一個“通用”的CallVirtualFun方法。

              這個通用方法和第三部分開始處的代碼有何聯(lián)系呢?聯(lián)系很大。由于A::fun()和A::fun2()是虛函數(shù),所以&A::fun和&A::fun2獲得的不是函數(shù)的地址,而是一段間接獲得虛函數(shù)地址的一段代碼的地址,我們形象地把這段代碼看作那段CallVirtualFun。編譯器在編譯時,會提供類似于CallVirtualFun這樣的代碼,當你調(diào)用虛函數(shù)時,其實就是先調(diào)用的那段類似CallVirtualFun的代碼,通過這段代碼,獲得虛函數(shù)地址后,最后調(diào)用虛函數(shù),這樣就真正保證了多態(tài)性。同時大家都說虛函數(shù)的效率低,其原因就是,在調(diào)用虛函數(shù)之前,還調(diào)用了獲得虛函數(shù)地址的代碼。

              最后的說明:本文的代碼可以用VC6和Dev-C++4.9.8.0通過編譯,且運行無問題。其他的編譯器小弟不敢保證。其中,里面的類比方法只能看成模型,因為不同的編譯器的低層實現(xiàn)是不同的。例如this指針,Dev-C++的gcc就是通過壓棧,當作參數(shù)傳遞,而VC的編譯器則通過取出地址保存在ecx中。所以這些類比方法不能當作具體實現(xiàn)

            本文來自CSDN博客,轉(zhuǎn)載請標明出處:http://blog.csdn.net/hujiangnet/archive/2008/11/01/3201746.aspx

            posted on 2011-05-06 13:02 厚積薄發(fā) 閱讀(379) 評論(0)  編輯 收藏 引用 所屬分類: C/C++

            導航

            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            統(tǒng)計

            常用鏈接

            留言簿

            隨筆分類

            文章分類

            文章檔案

            搜索

            最新評論

            久久久久久亚洲精品不卡| 久久人人爽人人爽人人片av高请| 国产精品久久久久影院嫩草| 曰曰摸天天摸人人看久久久| 久久精品成人| 久久国产高潮流白浆免费观看| 四虎国产精品免费久久5151| 久久婷婷五月综合97色直播| 99国产精品久久| 日韩美女18网站久久精品| 久久精品国产亚洲AV嫖农村妇女| 99久久免费只有精品国产| 少妇无套内谢久久久久| 精品久久久久久99人妻| 波多野结衣中文字幕久久| 久久久久久久免费视频| 热99re久久国超精品首页| 影音先锋女人AV鲁色资源网久久 | 亚洲国产精品18久久久久久| 99久久99久久精品国产| 性高湖久久久久久久久| 亚洲欧洲中文日韩久久AV乱码| 四虎国产永久免费久久| 99久久免费国产精精品| 人妻无码久久一区二区三区免费 | 久久精品国产99国产精品澳门| 青青草原综合久久大伊人导航 | 欧美日韩精品久久免费| 久久国产综合精品五月天| 久久国产精品久久| 国产成人精品久久免费动漫 | 色天使久久综合网天天| 中文字幕久久精品| 午夜精品久久久内射近拍高清| 久久久综合香蕉尹人综合网| 精品国产一区二区三区久久蜜臀| 中文字幕亚洲综合久久| 国产精品成人无码久久久久久| 国产日韩欧美久久| 久久精品国产精品亜洲毛片| 欧美亚洲另类久久综合婷婷|