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

            woaidongmao

            文章均收錄自他人博客,但不喜標(biāo)題前加-[轉(zhuǎn)貼],因其丑陋,見(jiàn)諒!~
            隨筆 - 1469, 文章 - 0, 評(píng)論 - 661, 引用 - 0
            數(shù)據(jù)加載中……

            當(dāng)“友元”遇到“虛函數(shù)”

            前些天d2school QQ群里有網(wǎng)友在討論這一內(nèi)容,我試圖做個(gè)整理。

            幾點(diǎn)基本知識(shí):

            1、如果類(lèi)A是類(lèi)B的友元,則類(lèi)A(的成員函數(shù))可以直接訪問(wèn)類(lèi)B的私有成員。

            2、友元不能繼承。也就是說(shuō),類(lèi)A是類(lèi)B的友元,類(lèi)D是類(lèi)B的派生類(lèi),則類(lèi)A并不會(huì)直接是類(lèi)D的友元。通俗一點(diǎn),父親的朋友,并不天生就是兒子的朋友。

            3、虛函數(shù)的基本知識(shí)就不說(shuō)了。

            來(lái)看下面的幾段代碼:

            Code:

            1.      class A; 

            2.       

            3.      class

            4.     

            5.      private

            6.          virtual void output() 

            7.          { 

            8.              cout << "B::output" << endl; 

            9.          } 

            10.       

            11.      friend class A;      

            12.  }; 

            13.   

            14.  class D : public

            15. 

            16.  private

            17.      virtual void output() 

            18.      { 

            19.          cout << "D::output" << endl; 

            20.      }     

            21.  }; 

            A B 的友元類(lèi), 而DB的派生類(lèi)。 所以,若想在A中直接訪問(wèn)D的代碼,則編譯不過(guò):

            Code:

            1.      class

            2.     

            3.      public:     

            4.          void test() 

            5.          { 

            6.              D d; 

            7.              d.output(); //編譯出錯(cuò) 

            8.          } 

            9.      }; 

            這一點(diǎn)大家都沒(méi)覺(jué)得有問(wèn)題,畢竟書(shū)上寫(xiě)得都明白直觀:父類(lèi)的友元,并不會(huì)因?yàn)槔^承,而成為派生類(lèi)的友元。

            但若代碼改成這樣,編譯器似乎就被欺騙了:

            Code:

            1.      class

            2.     

            3.      public:     

            4.          void test() 

            5.          { 

            6.              D d; 

            7.              B* pb = &d;    

            8.              pb->output(); //編譯通過(guò) 

            9.          } 

            10.  }; 

            沒(méi)錯(cuò),很多人會(huì)認(rèn)為這種代碼,就算能通過(guò)編譯器,也很可能是一種不好的代碼,因?yàn)樗趺纯炊枷袷窃谄垓_編譯器。是這樣嗎?先不討論。先問(wèn)一個(gè)問(wèn)題:上面的08行代碼,output調(diào)用的是B類(lèi)的那個(gè)output,還是D類(lèi)的那個(gè)呢?

            回答正確并不難——既然會(huì)認(rèn)定這段代碼帶有欺騙性質(zhì),而且又注意到output是一個(gè)虛函數(shù)的話——就能能正確地解答: 調(diào)用的是D類(lèi)的。A明明只是B的友元,但卻通過(guò)一個(gè)簡(jiǎn)單的類(lèi)型轉(zhuǎn)換,就訪問(wèn)了D類(lèi)的那個(gè)私有函數(shù),所以會(huì)覺(jué)得這是一種欺騙

            如果這是一種欺騙,那我們先來(lái)回答這個(gè)騙局為什么能成立:因?yàn)?span lang="EN-US">“友元的判斷(resolve),在編譯期決定;而虛函數(shù)在運(yùn)行期去resolve。在編譯08行代碼時(shí),編譯器看到*pb的類(lèi)型是B,而AB的友元,所以允許它調(diào)用output(它認(rèn)為是B::output);而在運(yùn)行時(shí),由于output是虛函數(shù),所以最終被決定到D::output頭上。

            沒(méi)時(shí)間細(xì)查手頭的《The Design and Evolution fo C++》,但不管這樣的設(shè)計(jì)是有意為之,還是無(wú)奈之舉,或者僅僅是C++眾多的特性正交現(xiàn)象之一,我個(gè)人覺(jué)得這個(gè)特性其實(shí)正是我們想要的。 

            一、首先要理解為什么派生類(lèi)不應(yīng)該繼承基類(lèi)的"友元",這一點(diǎn)很多C++的書(shū)講到了

            二、其次要理解它的語(yǔ)法機(jī)制:前面講的,一個(gè)編譯期屬性與一個(gè)運(yùn)行期屬性相遇了……

            三、要理解如果想關(guān)掉這一類(lèi)欺騙,其實(shí)做不到。且來(lái)看看,該法之一是在編譯期,也檢查實(shí)際調(diào)用對(duì)象的類(lèi)型,前面的示例代碼不難做到這一點(diǎn),但下面的代碼中,pb來(lái)自一個(gè)形參:

            Code:

            1.      A::test_2(B* pb) 

            2.     

            3.          pb->output(); //很難在編譯期反查出pb的實(shí)際類(lèi)型。 

            4.                       //因?yàn)檎{(diào)用test_2()的代碼,可能無(wú)處不在,甚至可能在未來(lái)的代碼 

            5.     

            四、要理解有時(shí)候,人們其實(shí)就是在故意做這種事,最典型的做法,就是通過(guò)非虛函數(shù)調(diào)用虛函數(shù): 

            Code:

            1.      class

            2.     

            3.      public

            4.          void Action() 

            5.          { 

            6.                   this->DoAction(); 

            7.          } 

            8.      private

            9.            virtual void DoAction() = 0; //一個(gè)私有的純虛函數(shù) 

            10.   

            11.      //  friend class A;

            12.  }; 

             任何一個(gè)合格的C++程序員,都應(yīng)該學(xué)會(huì)這種作法。DoAction是一個(gè)純虛函數(shù),這里我們更決絕一點(diǎn),干脆讓它是私有的,這就是逼著派生類(lèi)自己去實(shí)現(xiàn)一個(gè)完全自我的DoAction(),假設(shè)有個(gè)class D : public B,并且聽(tīng)話地實(shí)現(xiàn)了DoAction。具體D的定義,為節(jié)省點(diǎn)篇幅,不寫(xiě)了。

             注意到第11行的注釋?zhuān)?span lang="EN-US"> class A 現(xiàn)在已經(jīng)不是 B 的友員了,但不要緊,我們只是想在新版的類(lèi)A中,調(diào)用Action函數(shù),而它是public的,所以這里不需要友元來(lái)攪和。

            Code:

            1.      class

            2.     

            3.            void test() 

            4.            { 

            5.                     B* pb = new D; //pb 實(shí)際指向一個(gè)D對(duì)象。  

            6.                     pb->Action(); // Action  公開(kāi)的,所以可以調(diào)用 

            7.            } 

            8.      }; 

            pb 調(diào)用了非虛的B::Action函數(shù),但在Action內(nèi)調(diào)用了虛函數(shù)DoAction,再由于pb實(shí)際指向的是D對(duì)象,所以最終調(diào)用的是D::DoAction()——這了無(wú)新意對(duì)不對(duì)?只要學(xué)過(guò)一點(diǎn)C++的多態(tài),都會(huì)懂這一點(diǎn)。沒(méi)錯(cuò),它太司空見(jiàn)慣了,基本上所有C++程序員每天都會(huì)在寫(xiě)類(lèi)似的代碼——這就是我想說(shuō)的,有時(shí)候,看起來(lái)在調(diào)用基類(lèi)的代碼,但實(shí)際上在調(diào)用派生類(lèi)的代碼。假設(shè)我們修改了語(yǔ)法規(guī)則,逼著虛函數(shù)在遇上友元之后失效,那就是逼著程序員不去用friend,去將更多本來(lái)應(yīng)該是private的成員,用各種該法寫(xiě)成public的。

            五、接著,是一個(gè)看起來(lái)很簡(jiǎn)單,但卻被很多人誤解的概念:友元是破壞了封裝了嗎?錯(cuò),友元其實(shí)是促進(jìn)了更好的封裝。它基于這樣的需求:有一個(gè)類(lèi),它有那么幾個(gè)成員(數(shù)據(jù)或函數(shù)),它只能對(duì)個(gè)別的其它類(lèi)公開(kāi),這時(shí),你可以考慮使用友元這項(xiàng)技術(shù)。如果不用,會(huì)有很多人就把那些成員直接修改為public,結(jié)果:原本應(yīng)只對(duì)個(gè)別類(lèi)開(kāi)放的屬性,變成對(duì)所有類(lèi)開(kāi)放了。俗氣一點(diǎn),法律規(guī)定老婆可以在私有場(chǎng)合下看老公的屁屁,如果法律強(qiáng)制規(guī)定不允許有這種例外,那很可能會(huì)有一些哥們,直接把屁股public出來(lái)就上街了——你若問(wèn)他為什么,他也很無(wú)辜:我不過(guò)是想讓我老婆方便一些。

            六、讀了第五點(diǎn),對(duì)OO有一套的C++程序員要筆試/鄙視我了,好,好,我知道既使不用friend的屬性,也可以美滿地實(shí)現(xiàn)前述的,類(lèi)似老婆看老公屁股的問(wèn)題——我是說(shuō),通過(guò)OO技術(shù),避開(kāi)需要只對(duì)部分類(lèi)開(kāi)放權(quán)限的需求,而轉(zhuǎn)化為第三方(比如某個(gè)接口及它的實(shí)現(xiàn)類(lèi))中——不管如何,你必須承認(rèn)友元沒(méi)有破壞封裝性,因?yàn)槠渌鉀Q這一問(wèn)題的的,似乎更純粹的OO技術(shù),它們美好的地方是在于更細(xì)的類(lèi)顆粒,以及更好的類(lèi)組織(不美好地方是,效率差了點(diǎn),以及對(duì)OO思想非得有點(diǎn)水準(zhǔn),否則會(huì)繞暈掉,為了不讓Cer笑話,我們不提太多)。

            七、不過(guò),縱算如此(第六點(diǎn)),我還是是狡辯一句:就算是在那些看起來(lái)很純的OO語(yǔ)言里,其實(shí)也有友元的影子啊。比如Java,是沒(méi)有friend關(guān)鍵字,可以它的內(nèi)部類(lèi)(非靜態(tài)的內(nèi)部類(lèi)),可以直接訪問(wèn)外部類(lèi),難道不是友元嗎?——事實(shí)上,這也正是在C++使用friend最主旋律的用法(我甚至不用寫(xiě)之一)。再如Object Pascal(Delphi),是沒(méi)有friend關(guān)鍵字,可是只要是位于同一個(gè)代碼單元(就是同一個(gè).pas文件),則其中所有類(lèi)天生就是可以互相訪問(wèn)啊(當(dāng)然,需首先滿足可見(jiàn)性)——這是當(dāng)年我用Delphi時(shí)覺(jué)得最爽的地方之一了,既然號(hào)稱(chēng)更OO的語(yǔ)言都留了一手,為何C++不能呢? :)

            八、第七點(diǎn)明顯帶有情緒化,這不符合C++之父對(duì)我們的期望:C++設(shè)計(jì)中有一條指導(dǎo)原則,那就是,無(wú)論做什么事情,都必須相信程序員。與可能出現(xiàn)什么樣的錯(cuò)誤相比,更重要得多的是能做出什么好事情。C++程序員總被看作是成年人……。在C++的大千世界,差異性永遠(yuǎn)被尊重,有人不愛(ài)用template,那不用就是; 有人堅(jiān)決認(rèn)為只要有privatepublic就足夠了,那就把protected忘記吧,甚至有人認(rèn)為virtual也是多余的——很多人就是把C++當(dāng)成另一種C使用,那都可以接受。friend也一樣,如果你不用,它的存在并不會(huì)給你帶來(lái)什么性能損失,你要做的就是用很OO或很不OO的,但是你熟悉的方法去滿足友元的需求而已。

            九、一定要這個(gè)第9點(diǎn)。除了友元類(lèi),更常見(jiàn)的其實(shí)是友元函數(shù)。很多操作符的重載,都需要全局的友元函數(shù)來(lái)減輕相關(guān)類(lèi)的public出太多成員。這一下扯到操作符重載有用嗎?哇,這是另外一個(gè)經(jīng)典的問(wèn)題了,它曾經(jīng)引起的糾紛,比這個(gè)友元所帶來(lái)的,要熱鬧上幾倍呢。就此打住。

            十、最后一點(diǎn),C++初學(xué)者如何學(xué)習(xí)這門(mén)語(yǔ)言眾多的,又容易產(chǎn)生正交效應(yīng)的特性呢?我有個(gè)建議:先有個(gè)基本了解,做點(diǎn)練習(xí),但并不需要急著真正使用。

             

            posted on 2009-12-02 10:29 肥仔 閱讀(937) 評(píng)論(2)  編輯 收藏 引用 所屬分類(lèi): C++ 基礎(chǔ)

            評(píng)論

            # re: 當(dāng)&ldquo;友元&rdquo;遇到&ldquo;虛函數(shù)&rdquo;  回復(fù)  更多評(píng)論   

            好文~頂一個(gè)
            2009-12-02 13:26 | iceshark

            # re: 當(dāng)&ldquo;友元&rdquo;遇到&ldquo;虛函數(shù)&rdquo;  回復(fù)  更多評(píng)論   

            好文,學(xué)習(xí)了
            2009-12-02 16:01 | zhaoyg
            欧美亚洲国产精品久久蜜芽| 国产精品热久久毛片| 青草久久久国产线免观| 欧美国产精品久久高清| 欧美日韩精品久久久久| 久久精品国产色蜜蜜麻豆| 久久久久久亚洲精品成人| 国产精品成人久久久久久久| 无码国内精品久久综合88| avtt天堂网久久精品| 狠狠色综合网站久久久久久久| 亚洲午夜精品久久久久久app| a高清免费毛片久久| 久久久久亚洲AV无码观看| 亚洲av伊人久久综合密臀性色| 77777亚洲午夜久久多喷| 久久伊人影视| 久久97精品久久久久久久不卡| 欧美亚洲国产精品久久| 精品久久久中文字幕人妻| 久久综合精品国产一区二区三区| 久久精品国产影库免费看| 日产精品久久久久久久性色| 久久se精品一区二区影院| 国产精品久久99| 久久久久亚洲av无码专区| 久久精品免费全国观看国产| 国产精品99久久99久久久| 亚洲va久久久噜噜噜久久狠狠| 久久久国产99久久国产一| 亚洲欧美国产精品专区久久 | 99精品久久精品一区二区| 久久婷婷五月综合成人D啪| 久久99精品久久久久久噜噜| 国产亚洲欧美成人久久片| 国产精品久久影院| 久久精品国产亚洲AV香蕉| 欧美黑人激情性久久| 久久偷看各类wc女厕嘘嘘| 久久精品国产99久久无毒不卡 | 久久精品国产亚洲网站|