C++、Java和C#語言在處理“虛擬私有方法”上的差異

運行時動態綁定被調過程的“虛擬方法”(virtual method)機制是實現多態的關鍵技術。C++JavaC#(按出生年月排列,上同,下同)作爲三種主流的支持對面向對象的程序設計語言,自然都提供了這種動態的方法綁定機制,在這個問題上三兄弟沒有誰是含糊的。但當“虛擬”(virtual)和“私有”(private)碰在一起時,這幾種語言在處理上卻有所不同,本文的故事就是從一段小程序說起的:

我們來看下面這段短小的C++程序

 

程序很簡單,我們在基類“Base”中,通過公有方法f調用了虛擬的私有方法g,而繼承自BaseDerived類中覆寫(override)了私有的方法g

先不考慮其他,語法上有問題嗎?沒有;編譯能通過嗎?能。那運行結果是什麼呢?既然編譯能通過,那麼結果也就能猜到了,應該是:

Hi, MorningStar! I am g() of Derived.

 

沒錯,VC6.0VC.net 2003gcc都是這個結果。從結果來看,對私有方法g的調用在運行時綁定到了對象實際類型(Derived)的方法上,這符合虛擬函數的語義。

 

我們知道,在Java中,沒有“virtual”這麼個關鍵字,默認就是virtual,不可被覆寫的才需要加關鍵字“final”。Java中,對已被覆寫的方法如果想調用其父類的方法,則需通過特殊的關鍵字“super”調用。好了,不多說Java語法了,我們看下面一段類似的Java程序:

使用命令行

javac Derived.java

編譯,然後

Java Derived

運行,應該不會有什麼問題(偶是在Eclipse裏頭直接運行的:P)。結果爲:

Hi, MorningStar!, I am g() of Base.

 

雖然程序類似,但這兩中不同的語言產生的結果卻不一樣。

翻了翻C++98標準(ISO/IEC 14882)的相關章節,沒有找到任何關於此種情況的特殊描述,惟一涉及到虛擬函數訪問的第11.6節:Access to virtual functions,說的又不是這種情況。由此可見,C++語言中僅僅分別規定了publicprotectedprivate三者的訪問控制問題和virtual方法的動態綁定問題,而對於他們的組合,沒加任何限制,換句話說,前後是兩種無關的機制,訪問控制專門負責訪問權限的問題,不管方法是不是虛擬;而虛擬只管延遲綁定的問題,不管公有、保護還是私有。於是,前面的C++程序輸出那個結果也就不足爲怪了。

然而,從面向對象的角度考慮,基類中私有的東西對外界、對繼承類都是不可見的,繼承類根本不應該知道基類中任何私有的東西,於是繼承、覆寫也應無從談起纔對,即使方法重名,那也應該僅僅看作一種巧合,Java就是這麼做的。

Java中,private天生就是final的,我想Java的設計者至少有過上面的考慮:既然是私有,就不可見,見都見不到,何談覆寫?所以,在Java中,我們討論繼承和多態都是針對類的對外接口,包括puclic方法、protected方法和默認的friendly方法,而private是不被納入考量的,從某種意義上,Java中的private方法純粹是爲方法而方法,是一種組織代碼使之更清晰,更易維護的手段而已。

但如果僅從語法語義上考慮,而不是從面向對象的理論上考慮,或許C++更厚道一些——既然沒有限制,那麼private也可以virtual,既然可以virtual,那也就應該享受virtual級的待遇。而Java也沒什麼virtualvirtual,沒有任何語法痕跡便悶聲地規定:public/protected/friendly的是這樣,而private的是那樣。

 

最後,讓我們來看看時代的新寵,C#是怎麼做的呢?我們使用Visual Studio.net 2003新建Console應用程序,添加C#源文件,然後也可以寫類似的代碼:

  

編譯一下。完蛋了,編譯通不過!

.../Test.cs(14): Test.Base.g() : 虛擬成員或抽象成員不能是私有的

.../Test.cs(22): Test.Derived.g() : 虛擬成員或抽象成員不能是私有的

 

看來還是C#夠狠,語法上拒絕虛擬私有——那就沒啥輸出結果可以拷貝過來了。

C#C++那裏繼承了“virtual”關鍵字,而這一點跟Java只是形式上的不同,C#中非virtual方法在很大程度上就相當於Java中的final方法。只是Javafinal還用於指定類本身可否被繼承,C#使用另外一個關鍵字“sealed”做這件事。

相對來講,還是C++最靈活,它至少沒有限制這樣一種可能:基類允許派生類重新實現某個方法,卻不允許派生類直接調用基類的實現,即:我可以直接用你的(運行時綁定),但你不可以直接用我的——這一點跟protect/protected不一樣。而“重寫”與“調用”畢竟還是兩碼事,既然是兩碼事,分開考慮就不能說沒有必要。

 

總結:

C++從語法到語義上都支持可被覆寫的虛擬私有成員;

Java語法上看不出什麼痕跡,但語義上不存在可被覆蓋的私有成員;

C#從語法上直接拒絕私有方法成爲虛擬(可被覆寫)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章