自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 后端
虛函數(shù)和友元是C++中兩個很重要的概念,友元和虛函數(shù)經(jīng)常會在同一程序中出現(xiàn)。下面是幾點值得注意的知識。

友元可以是一個函數(shù),該函數(shù)被稱為友元函數(shù);友元也可以是一個類,該類被稱為友元類。虛函數(shù)必須是基類的非靜態(tài)成員函數(shù),其訪問權限可以是protected或public。

幾點基本知識:

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

2、友元不能繼承。也就是說,類A是類B的友元,類D是類B的派生類,則類A并不會直接是類D的友元。通俗一點,父親的朋友,并不天生就是兒子的朋友。

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

來看下面的幾段代碼:

Code:

  1. class A;  
  2. class B  
  3. {  
  4. private:  
  5. virtual void output()  
  6. {  
  7. cout << "B::output" << endl;  
  8. }  
  9. friend class A;  
  10. };  
  11. class D : public B  
  12. {  
  13. private:  
  14. virtual void output()  
  15. {  
  16. cout << "D::output" << endl;  
  17. }  
  18. };  
  19.  
 

 A 是 B 的友元類,而D是B的派生類。 所以,若想在A中直接訪問D的代碼,則編譯不過:

Code:

  1. class A  
  2. {  
  3. public:  
  4. void test()  
  5. {  
  6. D d;  
  7. d.output(); //編譯出錯  
  8. }  
  9. };  

 這一點大家都沒覺得有問題,畢竟書上寫得都明白直觀:父類的友元,并不會因為繼承,而成為派生類的友元。

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

Code:

  1. class A  
  2. {  
  3. public:  
  4. void test()  
  5. {  
  6. D d;  
  7. B* pb = &d;  
  8. pb->output(); //編譯通過  
  9. }  
  10. }; 

 沒錯,很多人會認為這種代碼,就算能通過編譯器,也很可能是一種不好的代碼,因為它怎么看都像是在欺騙編譯器。是這樣嗎?先不討論。先問一個問題: 上面的08行代碼,output調用的是B類的那個output,還是D類的那個呢?

回答正確并不難——既然會認定這段代碼帶有“欺騙”性質,而且又注意到output是一個“虛函數(shù)”的話——就能能正確地解答: 調用的是D類的。A明明只是B的友元,但卻通過一個簡單的類型轉換,就訪問了D類的那個私有函數(shù),所以會覺得這是一種“欺騙”。

如果這是一種欺騙,那我們先來回答這個騙局為什么能成立:因為“友元”的判斷(resolve),在編譯期決定;而虛函數(shù)在運行期去resolve。在編譯08行代碼時,編譯器看到*pb的類型是B,而A是B的友元,所以允許它調用output(它認為是B::output);而在運行時,由于output是虛函數(shù),所以最終被決定到D::output頭上。

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

一、首先要理解為什么派生類不應該繼承基類的"友元",這一點很多C++的書講到了

二、其次要理解它的語法機制:前面講的,一個編譯期屬性與一個運行期屬性相遇了……

三、要理解如果想關掉這一類欺騙,其實做不到。且來看看,該法之一是在編譯期,也檢查實際調用對象的類型,前面的示例代碼不難做到這一點,但下面的代碼中,pb來自一個形參:

Code:

  1. A::test_2(B* pb)  
  2. {  
  3. pb->output(); //很難在編譯期反查出pb的實際類型。  
  4. //因為調用test_2()的代碼,可能無處不在,甚至可能在未來的代碼  
  5. }  
  6.  

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

Code:

  1. class B  
  2. {  
  3. public:  
  4. void Action()  
  5. {  
  6. this->DoAction();  
  7. }  
  8. private:  
  9. virtual void DoAction() = 0; //一個私有的純虛函數(shù)  
  10. // friend class A;  
  11. };  

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

注意到第11行的注釋, class A 現(xiàn)在已經(jīng)不是 B 的友員了,但不要緊,我們只是想在新版的類A中,調用Action函數(shù),而它是public的,所以這里不需要友元來攪和。

Code:

  1. class A  
  2. {  
  3. void test()  
  4. {  
  5. B* pb = new D; //pb 實際指向一個D對象。  
  6. pb->Action(); // Action 是 公開的,所以可以調用  
  7. }  
  8. };  
  9.  

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

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

六、讀了第五點,對OO有一套的C++程序員要“筆試/鄙視”我了,好,好,我知道既使不用friend的屬性,也可以美滿地實現(xiàn)前述的,類似老婆看老公屁股的問題——我是說,通過OO技術,避開需要只對部分類開放權限的需求,而轉化為第三方(比如某個接口及它的實現(xiàn)類)中——不管如何,你必須承認友元沒有破壞封裝性,因為其它解決這一問題的的,似乎更純粹的OO技術,它們美好的地方是在于更細的類顆粒,以及更好的類組織(不美好地方是,效率差了點,以及對OO思想非得有點水準,否則會繞暈掉,為了不讓Cer笑話,我們不提太多)。

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

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

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

十、***一點,C++初學者如何學習這門語言眾多的,又容易產生正交效應的特性呢?我有個建議:先有個基本了解,做點練習,但并不需要急著真正使用。

友元的作用是提高了程序的運行效率,即減少了類型檢查和安全性檢查等都需要時間開銷,但它破壞了類的封裝性和隱藏性,使得非成員函數(shù)可以訪問類的私有成員。而虛函數(shù)的作用則是為了實現(xiàn)多態(tài)。虛函數(shù)和友元總是會同時出現(xiàn)在一段程序中,希望通過本文,能解決你的困惑。

【編輯推薦】

  1. C++友元函數(shù)基本應用方式解析
  2. 13.1.7 友元
  3. 9.6.1 虛函數(shù)的概念
  4. 實現(xiàn)C++虛函數(shù)時相關注意事宜
責任編輯:于鐵 來源: 天極網(wǎng)
相關推薦

2024-01-29 16:47:44

函數(shù)封裝開發(fā)

2011-07-15 13:49:30

C++友元函數(shù)友元類

2015-07-10 15:31:42

ITIoT物聯(lián)網(wǎng)

2010-02-02 17:33:17

C++友元函數(shù)

2015-09-18 15:22:56

DCIMITSM

2011-03-01 10:58:00

2014-04-14 10:21:15

開發(fā)運維DevOps

2010-09-01 15:42:39

DHCP SnoopiARP

2009-06-17 13:26:06

scala繼承模型

2022-05-17 14:34:23

元宇宙數(shù)字經(jīng)濟商業(yè)

2018-02-27 13:59:52

春節(jié)互聯(lián)網(wǎng)直播

2013-07-26 11:17:34

AdTime游戲大數(shù)據(jù)

2009-06-19 18:00:05

HibernateSpring

2010-04-29 14:33:01

Unix系統(tǒng)

2010-01-18 17:38:54

C++虛函數(shù)表

2017-01-13 10:33:39

華為大數(shù)據(jù)

2015-12-31 15:19:14

霧霾高性能英特爾

2024-06-11 12:38:12

2019-06-24 16:30:33

區(qū)塊鏈零知識證明比特幣

2024-06-18 13:32:08

點贊
收藏

51CTO技術棧公眾號