前言
“Java 和C++ 中子類對父類函數覆蓋的可訪問性縮小的問題”的題目看起來比較學術化,但的確是一個容易忽視的問題。本文力求詳細闡述這一問題在Java 以及C++ 中的區別。
先介紹什麼是“子類對父類函數覆蓋的可訪問性縮小”。對於繼承而言,子類可以覆蓋父類的“虛函數”――儘管Java 中沒有虛函數這一術語,但可以把Java 的所有函數都看作虛函數,因為Java 的所有函數都可以被子類覆蓋。這裡僅借用“虛函數”這一名詞的含義,不深究語言的細節。 Java 和C++ 都允許在覆蓋時,改變函數的可訪問性。所謂“可訪問性”,就是使用public 、 protected 、 private 等訪問控制符進行修飾,用來控制函數能否被訪問到。通常可訪問性的順序為(由於C++ 中沒有包的概念,因此暫不考慮包訪問控制符,這並不影響這裡的討論):
public > protected > private
以Java 為例:
class Base { protected void sayHello() { System.out.println("Hello in Base"); }}class Child extends Base { public void sayHello() { System.out.println("Hello in Child"); }}注意:這裡的sayHello()函數。父類Base 中,該函數使用protected 訪問控制符進行修飾。而子類將其改用public ,這不會有任何問題。 子類對父類函數覆蓋時,擴大可訪問性,通常都不是問題。
當子類對父類函數覆蓋的可訪問性縮小時,Java 和C++ 採取了不同的策略。
首先以Java 為例,看下面的代碼:
class Base { public void sayHello() { System.out.println("Hello in Base"); }}class Child extends Base { private void sayHello() { System.out.println("Hello in Child"); }}上面的代碼中,高亮的第8 行會有編譯錯誤――這段代碼根本不能通過編譯! Java 不允許子類在覆蓋父類函數時,縮小可訪問性。 至於原因,我們可以用一個例子來說明。例如我們在類外部寫下面的代碼:
Base base = new Base();base.sayHello();base = new Child();base.sayHello();
假如之前的代碼可以通過編譯,那麼就存在這麼一種可能:當base 指向new Base() 時, sayHello() 是可以訪問到的,但是當base 指向new Child() 時, sayHello() 卻無法訪問到!在Java 看來這是一個矛盾,應該避免出現這種問題,因此,Java 從編譯器的角度規定我們不能寫出上面的代碼。
針對C++,情況又有所區別。來看C++ 的例子:
class Base {public: virtual void sayHello() { std::cout << "Hello in Base"; }}class Child : public Base {private: void sayHello() { std::cout << "Hello in Child"; }}這段代碼在C++ 中是完全正確的。注意,這裡的子類在覆蓋父類函數時, 縮小了可訪問性。如果你沒有看出有什麼問題,那麼我們完全可以在類外部寫出下面的代碼:
Child child;child.sayHello(); // 不能通過編譯,因為sayHello() 是private 的static_cast<Base&>(child).sayHello(); // 可以通過編譯,因為sayHello() 是public 的
第2 行調用是失敗的,因為在Child 中, sayHello()是private 的,不能在外部調用。然而,當我們使用static_cast 將Child 強制轉換成Base 對象時,事情發生了改變――對於Base 而言, sayHello()是public 的,因此可以正常調用。
針對這一點,C++ 標準的Member access control 一章中的Access to virtual functions 一節可以找到如下的例子:
class B {public: virtual int f();};class D : public B {private: int f();};void f() { D d; B* pb = &d; D* pd = &d; pb->f(); // OK: B::f() is public, D::f() is invoked pd->f(); // error: D::f() is private}對此,C++ 標准給出的解釋是:
Access is checked at the call point using the type of the expression used to denote the object for which the member function is called ( B* in the example above). The access of the member function in the class in which it was defined (D in the example above) is in general not known.
簡單翻譯過來有兩條要點:
正因如此,C++ 的調用方似乎可以通過一些技巧性轉換,“巧妙地”調用到原本無法訪問的函數。一個更加實際的例子是:Qt 裡面, QObject::event()函數是public ,而其子類QWidget 的event()函數則改變成protected 。具體可以閱讀Qt 的相關代碼。
總結來說,在子類覆蓋父類函數時,Java 嚴格限制了子類不能縮小函數可訪問性,但C++ 無此限制。個人認為,從軟件工程的角度來說,Java 的規定無疑更具有工程上面的意義,函數的調用也更加一致。 C++ 的標準則會明顯簡化編譯器實現,但是對工程而言並不算很好的參考。
PS: C++ 標準的正式版是需要購買的,但是草案可以免費下載。 C++ 標準草案的下載地址可以在下面的頁面找到: https://isocpp.org/std/the-standard
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。