虚拟函数是否应该被声明仅为private/protected?
問題導(dǎo)入?
我想對于大家來說,虛擬函數(shù)并不能算是個陌生的概念吧。至于怎么樣使用它,大部分人都會告訴我:通過在子類中重寫(override)基類中的虛擬函數(shù),就可以達到OO中的一個重要特性——多態(tài)(polymorphism)。不錯,虛擬函數(shù)的作用也正是如此。但如果我要你說一說虛擬函數(shù)被聲明為public和被聲明為private/protected之間的區(qū)別的話,你又是否還能象先前一樣肯定地告訴我答案呢?
其實在一開始,我和大家一樣,都喜歡把虛擬函數(shù)聲明為public(我并沒有做太多的調(diào)查就說了這些,因為我身邊的程序員們幾乎都是這樣做的)。這樣做的好處很明顯:我們可以輕而易舉地在客戶端(client,相對于server,server指的是我們所使用的繼承體系架構(gòu),client指的就是調(diào)用該體系中方法/函數(shù)的外部代碼)調(diào)用它,而不是通過利用那些煩人的using聲明,或是強加給類的friend關(guān)系來滿足編譯器的access需求。OK,這是一個很不錯的做法,簡單、并且還能達到我們的要求。
但根據(jù)OO三大特性中的另一個特性——封裝(encapsulation)來說(另一就是繼承),需要我們將界面(interface)與實作(implementation)分開,即向外表現(xiàn)為一個通用的界面,而把實作上的細節(jié)封裝在模塊內(nèi)不讓client端知曉。界面與實作的分離,使得我們得以設(shè)計出耦合度更低、擴展性更好的系統(tǒng)來,并且還可以從這樣的系統(tǒng)中提取出更多的可重用(reusable)的設(shè)計。
對于OO來說,封裝是它的頭等大事,享有最高的權(quán)利,其他的設(shè)計如果和它有著沖突,則以符合它的設(shè)計為準。這樣,問題就出來了,萬一我們所希望出現(xiàn)的多態(tài)正好是具體的實作細節(jié)并且我們不希望把它暴露給client端的話,那我們應(yīng)該怎么樣改動我們的設(shè)計以使得它能夠適應(yīng)封裝的需求呢?
可行的解決辦法?
幸好,C++中不但支持public的虛擬函數(shù),也有著private/protected虛擬函數(shù)。(在此我不想對于public和private/protected之間的區(qū)別多說。)前者是我們常用的形式,我也不多說,我們在此主要關(guān)心的是private/protected的虛擬函數(shù)。
你可能會有疑惑,既然虛擬函數(shù)被聲明為private(protected不算,因為子類可以直接訪問基類的protected成員),那子類中怎么還能對它進行重寫呢?在此,你的疑慮是多余的,C++標準(也稱ISO 14882)告訴我們,虛擬函數(shù)的重寫與它的具體存儲權(quán)限沒有任何關(guān)系,即便是聲明為private的虛擬函數(shù),在子類中我們也同樣可以重寫它。因此,碰到上面所說的問題,我們就可以得到如下的設(shè)計:
class Base {
public:void do_something(){//......really_do_something();//...... } private: virtual void really_do_something() { //do the polymorphism code here } }; class Derived: public Base { private: void really_do_something() { //do the polymorphism code here } }; - 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
如果我們需要從上面的設(shè)計中得到實際上的多態(tài)行為,只要象下面一樣調(diào)用do_something就可以了:
//client code
Base& b; //or Base* pb;
b.do_something(); //or pb->do_something(); - 1
- 2
- 3
- 1
- 2
- 3
這樣我們就得以解決了在開始處提出的那個問題。
問題引申?
那就這樣完結(jié)了嗎?沒有。相反,至此我們才開始進行我們今天的討論。首先讓我們來看看多態(tài)的實現(xiàn):
void Base::do_something(){//......really_do_something();//......} - 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
我們可以發(fā)現(xiàn),在調(diào)用真正對多態(tài)有貢獻的really_do_something()之前及調(diào)用后,我們還可以在其中添加我們自身的代碼(如一些控制代碼等),這樣我們“好像”就可以輕而易舉地實現(xiàn)了Bertrand Meyers所提出的“Design By Contract”(DBC)[1]了:
void Base::do_something(){//our precondition code herereally_do_something();//our postcondition code here} - 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
然后,讓我們在去看看Template Method這個Pattern[2],發(fā)現(xiàn)所謂的Template Method也主要就是通過這種方式來進行的。于是,我們是否可以這么想呢:將所有的虛擬函數(shù)都聲明為private/protected,然后再使用一個public的非虛擬函數(shù)調(diào)用它,這樣,我們不就得到了上面所列出的所有好處嗎?
詳細分析?
簡單看來,好像那么做真的是好處大大的,既不會造成效率上的損失(通過將該public的非虛擬函數(shù)inline化,簡單的函數(shù)轉(zhuǎn)調(diào)用的開銷就可以被消除掉),又能夠獲得上述所有的好處。何樂而不為呢??
實際上來看,有不少程序員也正是這么做的(Herb Sutter所調(diào)查的結(jié)果表明,這里面甚至還包括那些實作標準函數(shù)庫的程序員們,當然,他們所考慮到的使用這種技巧的理由不會僅僅是我下面所給出的其他人的理由^_^)。有的人甚至還認為,虛擬函數(shù)就應(yīng)該被聲明為private/protected(當然,虛擬的析構(gòu)函數(shù)不能夠算在其中之列,否則就會有大亂子了)。?
但讓我們再仔細地考慮一下,想想一些比較極端的例子。假設(shè)我們有一個類,它擁有的虛擬函數(shù)的個數(shù)非常之多(就算它10000個吧),那即使大多數(shù)情況下只是簡單的函數(shù)轉(zhuǎn)調(diào)用動作,我們是否還應(yīng)該為它的每一個虛擬函數(shù)都提供一個公開的非虛擬的界面呢?這時,為你的程序提供一個接口類(即沒有任何成員變量,所有的方法都是純虛函數(shù)的類)是一個不錯的解決方案。?
還有,因為這樣做的結(jié)果將會是:基類中的那個public的非虛擬界面函數(shù)必須能夠適合所有的子類的情況,這樣,我們將所有的責任都推倒基類上去了,這不能算是一個好的設(shè)計方法。假設(shè)我們有了一個繼承體系極深的架構(gòu),在對基類進行了多次繼承后,我們突然發(fā)現(xiàn),新的子類已經(jīng)無法適應(yīng)原有的那個界面了。于是,為了繼續(xù)執(zhí)行我們的虛擬函數(shù)private化,我們就將不得不把基類的代碼給翻出來并改正它。幸運點的是,基類的代碼是我們可以得到的,這樣我們最起碼還是有機會改正的(雖然有的時候,我們已經(jīng)無法看懂基類中的代碼了);糟糕的是,我們的基類是通過我們使用的一個函數(shù)庫中得到的,而該函數(shù)庫的代碼我們無法獲得,這個時候我們該怎么辦呢?由此可見,如果在設(shè)計可能會被進行深度繼承的類繼承體系架構(gòu)時,要想繼續(xù)使用private的虛擬函數(shù)的話,對于設(shè)計基類的要求就將會變的非常之高(因為在以后,基類的任何小小改動造成的后果傳遞到了繼承的低端時都將被顯著的放大),而讓設(shè)計人員去猜測以后所有的可能使用情況是件不現(xiàn)實的事情,這樣也就容易產(chǎn)生脆弱的、需要被頻繁改動的設(shè)計。請記住一點:FBC(Fragile Base Class)是一件可怕的事情,在我們的程序中應(yīng)當避免出現(xiàn)這種情況。?
另外,在你決定把你程序中的虛擬函數(shù)改為private/protected前,你有沒有一個很好的理由呢?如果你只是說:“哦,我不知道,不過這樣做可能會在以后的某天產(chǎn)生作用”。不錯,時刻讓自己的程序保持可擴展性是很好的一件事情,但那都是基于你可以預(yù)見未來的擴展之上的(這種預(yù)見主要來自于你對于該領(lǐng)域的深刻認識或是你平時的經(jīng)驗)。在沒有任何理由的情況下,僅僅靠著一句“它以后可能會有用”就往自己的程序中添加進去某種特性聽起來好像很炫,但實際上它可能對你的程序有百害而無一利。在我們現(xiàn)有的各種Framework中,有著很多類似的“以后可能會有用”的特性,結(jié)果最終都被證明為沒有被使用到,這不能不說是對于開發(fā)工作的一種浪費。因此,還是讓我們記住在XP[3]中所說的YNGNI(You Never Going to Need It),對于現(xiàn)階段沒有用到的特性,還是不要提供為好。不過,如果你能夠預(yù)見到以后的擴展的話,還是請你為它留下一個可擴展的便利。?
此外,基于編譯器的角度來看,當你一旦改動了基類,那么所需要重新編譯的就不僅僅是基類本身了,所有從該基類繼承下來的派生類也都將被重新編譯。這樣,我們就不得不又浪費掉大量的編譯時間了。尤其是當我們決定大量使用inline的方式來轉(zhuǎn)調(diào)用時,所需的時間就更加多了(因為inline函數(shù)在編譯時會被擴展成實際的調(diào)用代碼)。這也可以算是一種語法上的FBC問題。此外,當你決定向你的繼承體系中增加一個函數(shù),并改變了基類接口的行為,你就有可能破壞了整個繼承體系,并使得外部的client端代碼也受到了沖擊。這種情況可以算是一種語義上的FBC問題。請記住:穩(wěn)定的代碼永遠不要建立在不穩(wěn)定的代碼基礎(chǔ)之上。?
現(xiàn)在,再讓我們回到Template Method上面來看。什么時候該使用TM呢?從Design Patterns中得到它的意圖為:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。這和我們所談?wù)摰奶摂M函數(shù)是不是應(yīng)該為private/protected完全是不相干的,雖說在實現(xiàn)TM時我們會用到private/protected的虛擬函數(shù),但并不是所有的private/protected virtual都為TM。?
最后,完全使用private/protected virtual還有一個問題就是:OO中所提倡的彈性。我們知道,OO中的彈性通常都是由繼承中的多態(tài)提供的,但有時我們也會使用組合中的委托。實際上已經(jīng)有很多的Patterns都是這么做的了,如:Proxy, Adapter, Bridge, Decorator等。如果一味地追求private/protected virtual,勢必使得我們只能在程序中使用繼承了,為了一棵樹而放棄一片森林的事情,我想大家也都不愿意做吧。
結(jié)論?
說了半天,我也該收工了:-)現(xiàn)在開始進行我觀點的歸納:?
一般說來,把虛擬函數(shù)聲明為private/protected是一個很不錯的設(shè)計方法[4],但如果一旦把它作為一個唯一的Sliver Bullet來使用的話,就會產(chǎn)生許許多多的問題。在這篇文章中我也只是大概的談了其中的部分,還有其他的一部分內(nèi)容由于現(xiàn)今還沒有完全整理好,也就不多說了。希望能夠在下次再把它完善掉。
參考資料?
1、Object-Oriented Software Construction,Second Edition, Bertrand Meyer,清華大學出版社出版(影印版)?
2、設(shè)計模式可復(fù)用面向?qū)ο筌浖幕A(chǔ), GoF, 李紅軍等譯,機械工業(yè)出版社出版?
3、Conversations: Virtually Yours, Herb Sutter & Jim Hyslop, CUJ?
以及網(wǎng)絡(luò)上相關(guān)的資料
后記?
寫該文的最初沖動來源于newsgroup: comp.lang.c++.moderated上面的一個討論:Virtual methods should only be private or protected?在觀看了Kevlin Henney,Herb Sutter以及James Kanze等幾位大師的精彩言論后,總想把自己的感受寫下來。在一開始,我倒是寫了很多,但沒有完全寫完。近來由于比較忙的情況,因此也就慢慢地把此事差點給忘記了。不是蟲蟲催著我要稿的話,我想也不知道要到什么時候我才能把它給寫完:-(,即便是現(xiàn)在,由于很久沒有復(fù)習這些資料,很多的東西也沒能寫進去,如果大家覺得意猶未盡的話,可以直接到newsgroup中找到該thread,里面有著完整的討論內(nèi)容。
[1] 《Object-Oriented Software Construction》 Chapter 11:Design by Contract: building reliable software,國內(nèi)有該書的影印版出售。?
[2] 《Design Patterns: Elements Of Reusable Object-Oriented Software》,國內(nèi)有該書的中文翻譯版售?
[3] Extreme Programming,一種輕量級的軟件開發(fā)方式,注重開發(fā)中的靈活性,測試及其他……可以從下面網(wǎng)站上得到有關(guān)它的更多信息:www.extremeprogramming.org?
[4] 可以參見于Herb Sutter和Jim Hyslop發(fā)表的Conversations: Virtually Yours一文,在CUJ站點上可以找到這篇文章,此外,在csdn中也有過它的譯文。
轉(zhuǎn)載于:https://www.cnblogs.com/yzl050819/p/6844035.html
總結(jié)
以上是生活随笔為你收集整理的虚拟函数是否应该被声明仅为private/protected?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 荷花作者是谁啊?
- 下一篇: VueJs开发笔记—IDE选择和优化、框