關於C 物件導向設計的訪問性問題詳解

2022-09-29 21:48:21 字數 4311 閱讀 2827

前言

最近在看scott meyers大神的《effective c++》和《more effective c++》,雖然這兩本書都是古董級的教參了(當然針對c++11/c++14作者所更新的《modern effective c++》英文已經發售了,不過還沒中文翻譯版本),但是現在看來仍然收益匪淺,而且隨著對這個複雜語言了解的深入和實踐專案經驗的增加,www.cppcns.com很多東西和作者產生了一種共鳴,以前種種疑惑突然有種撥雲霧而見天日、豁然開朗的感覺,也難怪被列為合格c++程式設計師之必讀書目。其實c++確實是個可怕的語言,於是市面上針對這個語言的教參也是聆郎滿目層出不窮,當然水平也是參差不齊,像上面所說的meyers三部曲能夠歷久彌新,也凸顯了這些經典教參的真正價值。

至於最近回歸c++本質,主要是覺得現在後台開發的rpc、mq、分布式系統雖然被稱的神乎其神的,但是作為成熟的元件絕大多數公司都可以是直接拿來主義,當然也不可否認其使用經驗的可貴,因為最近線上使用這些元件還是遇到或多或少不少問題的,以後可以少走些坑,然而這種東西也是可遇難求的;反而c++語言本身的使用占用了程式設計師絕大多數的工作內容,從而直接影響到專案的質量和後續的可維護性。在此,侯捷老師的 勿在浮沙築高台 仍如警世名言響徹在耳,乙個合格的程式設計師其紮實的基本功是多麼重要。

c++物件導向的東西太多了:public、protected、private訪問和繼承,virtual和多型、多繼承,外加const、預設引數、名字查詢等,光這些元素的排列組合就可以匯出很多種情況,看似靈活多變,但不是每種情況都值得去嘗試的。

一、public繼承

public繼承意味著是」is-a」的關係,每個派生型別物件也是乙個基類型別物件,基類支援的操作派生類都支援,只不過派生模擬基類更具體化一些而已,否則的話應該將派生類不支援的特性給踢出去,比如:

class bird ;

class flyingbird: public bird ;

class penguin: public bird ;

所以,總體來說public繼承是相對比較嚴格的契約關係。當然public繼承是乙個比較籠統的概念,細分下來還包括介面繼承、實現繼承、介面和實現繼承。

如果基類宣告了乙個pure virtual函式,則其目的是讓派生類只繼承該函式介面;如果基類宣告了乙個impure virtual函式,就是讓派生類繼承該函式的介面和其預設實現;如果某個成員函式是non-virtual函式,則意味著它不打算在派生類中有不同的行為,即派生類繼承該函式介面及乙份強制性實現。

對於pure virtual函式的介面宣告,基本沒有什麼意義,而non-virtual成員也顯而易見。不過對於impure virtual虛函式,看似提供了預設實現使用起來會比較方便,而且派生類可以覆蓋其實現也比較靈活,但是如果直接使用這種方式,那麼如果基類產生了新的派生類,但是恰好派生類忘記對這個impure virtual函式進行override,而其預設實現又不滿足新派生類的行為,那麼新派生類物件的程式設計客棧呼叫將會引發問題。所以如果想繼承介面,同時又提供預設實現,那麼比較好的方式是將這兩個功能進行分離,用乙個pure virtual函式提供介面,再用乙個non-virtual protected函式提供預設實現,而讓派生類手動確認是否使用該預設行為。

class airplane

};除了上面的方式處理impure virtual的預設實現,其實也可以將其轉換為:仍然使用pure virtual函式宣告介面,不同同時也提供其預設定義,這樣派生類在override這個pure virtual介面的時候既可以完全重新定義fly的行為,也可以直接一條語句用基類名字直接呼叫基類的預設實現(airplane::fly),其好處是不用引入乙個新的函式名字,缺點是預設實現成了public的了。

說到此處,應該對c++中介面繼承的行為得以了解了。

二、虛函式外的其他選擇

前面我們說到了《c++之virtual函式訪問性》中談及了nvi手法,算是對public virtual的乙個強有力的替換工具,不過我們知道其本身也用到了虛函式。虛函式具有執行時開銷,而且其實現也是編譯時間確定執行時候選擇,在有些情況下其靈活性還是受限。

相比於虛函式依據派生型別進行行為的定製化之外,strategy策略模式顯得更為的靈活。通過在物件內部儲存函式指標(或者更泛化的boost::function函式物件),其行為可以依據具體物件差異化而非派生型別差異化,甚至通過set介面其行為還可以在執行時候進行變更。雖然meyers說明如果使用非成員函式,預設將不能訪問類的私有成員,否則就需要對封裝性進行一定程度的妥協鬆懈,但是通過boost::function+boost::bind這個強有力的工具,使用繼承體系中的成員函式也是十分方便的。

此處本人感覺,雖然設計模式被奉為c++開發的經典,但是隨著modern c++在標準上引入更多的特性和功能,c++的開發將必定變的更加友好直觀,也不被過於墨守那古典23式了,畢竟絕大多數的設計模式都通過繼承來實現的,不可避免的增加了程式開發和維護的複雜度。

三、繼承體系來的其他問題

好了,輕鬆愉快的東西結束了,下面是c++史上的黑暗時刻了。

3.1 繼承而來名字的可見性

c++具有一套名字查詢的規則,總體來說就是從區域性到外圍,從派生類到基類,從內層名字空間到全域性名字空間的查詢順序。

由於到此為止我們沒有說明函式過載的情況,所以你此時仍然安之若素:對於public non-virtual函式我們不去重寫,對於virtual函式我們可以override,這一切安好,但是一旦考慮到相同函式名的過載問題,c++有一套理論就會讓你暈乎了:c++防止在應用程式庫或者應用框架中建立的新的派生類被附帶從疏遠的基類中繼承而來的過載函式,所以在繼承的時候c++不會將基類的名字自動匯入到派生類中。

好了,這就說明,之前繼承而來的介面,其實也是在使用的時候在派生類作用域中沒有找到該符號,而在基類中找到該符號後滿足呼叫的,而如果你在基類中定義了其某個過載版本(無論是virtual還是non-virtual)的時候,c++在名字查詢的時候就在你的派生類作用域中找到該名字了,然後進行型別檢查和過載,但是過載的版本只限於在派生類**現的版本,基類的版本不參與過載!!!

所以,在派生類中想增加還是改寫無論virtual還是non-virtual函式的過載版本,第一件事是使用using宣告將基類符號的所有版本宣告到派生的名字作用域中,然後再幹其他的。

3.2 絕不重新定義繼承而來的non-virtual函式

c++的non-virtual函式都是編譯期靜態繫結的,其名字查詢從其指標的靜態型別開始。

任何情況下,都不要重新定義乙個繼承而來的non-virtual函式,否則其呼叫的版本決定於其指標靜態型別,這與public繼承is-a的一致性關係相互違背。

3.3 絕不重新定義繼承來的預設引數值

因為上面說到我們不應該重新定義乙個繼承而來的non-virtual函式,所以到這裡我們可以說:絕對不要重新定義乙個繼承而來帶預設引數值的virtual函式的引數預設值。其原因是:virtual函式是動態繫結stvetsfe的,而預設引數是靜態繫結的。

所以如果基類和派生類的引數預設值不一致,則使用引用、指標呼叫發生引數預設值靜態繫結和呼叫函式體動態繫結將會非常的詭異,所以需要避免這種情況。還有就是如果虛函式引數再基類指定的引數預設值,而派生類override的時候沒有指明引數預設值,此時如果客戶端以派生類物件方式呼叫該函式,則發生的是靜態繫結,需要顯示指定引數值;而如果客戶端以指標、引用的新式呼叫該函式,則發生的是動態繫結,可以不指定其帶有預設值的引數。

class shape ;

class circle: public shape ;

解決這stvetsfe個問題的乙個方式是使用nvi手法,其public non-virtual介面提供預設預設值(且不會被派生類重寫),而private virtual不使用預設預設的特性以規避這種可能的不一致性。

3.4 private繼承

private繼承沒有」is-a」的契約關係了,在使用上乙個巨大的差異是:編譯器不再會自動將乙個派生類物件轉換為乙個基類物件了,這意味著原本接收基類物件的函式引數將不再能夠為其傳遞派生類物件作為實參了(物件、引用、指標型別都不允許,編譯器會報基類s是派生類t不可訪問的基類);同時由基類繼承而來的所有成員,在派生類中都會變成private的訪問許可權。

private繼承意味著只有實現部分被繼承,介面部分被全部略去了,所以private繼承應當是採用基類的某些功能幫助派生類完善其功能,從某種情況下說具有」has-a」的符合型別,所以除了考慮到派生類需要訪問基類protected成員和virtual的因素被牽扯進來,否則應該盡量使用組合型別來代替private繼承,而且即使如此,也可以使用下面的手法瞞天過海:

class timer ;

class widget ;

widgettimer timer;

};關於protected繼承,連meyers大神都沒用過,那麼我又何必廢腦經去考慮他……

參考effective c++

總結本文標題: 關於c++物件導向設計的訪問性問題詳解

本文位址: /ruanjian/c/202712.html

C 物件導向設計

一.組合 復合 繼承,委託 1.composition 組合 has a 1.1 組合舉例 adapter 設計模式 關係 利用deque功能實現所有queue功能 template class queue size type size const reference front reference...

C 物件導向設計

一.組合 復合 繼承,委託 1.composition 組合 has a 1.1 組合舉例 adapter 設計模式 關係 利用deque功能實現所有queue功能 template class queue size type size const reference front reference...

物件導向中訪問許可權問題

訪問限制 限制別人呼叫某一些屬性或者函式 好處 提高 的安全性 做法 在名字前面加2個下劃線 如果要賦值或者訪問就必須提供set 或者get 函式class person def init self self.name none self.age none def setage self,age i...