文章目錄
- 條目7:為多態基類聲明virtual解構函式
- 條款9:不要在建構函式或解構函式中調用虛函數
- 條款13~15 通過對象管理資源
- 條款23: 以non-member non-friend函數代替member函數
- 條款30:inline函數
Effective C++ 已經看過若干編了,裡面基本都是的是C++使用最基本的準則,許多條目已經被奉為標準。最近翻看,將一些有感覺的知識點簡要記錄一下。
條目7:為多態基類聲明virtual解構函式
一個類如果將會以多態的方式使用,那麼就要提供一個virtual dtor。這樣才能保證相關的對象被正確銷毀。但粗暴地為所有的類提供虛解構函式也是錯誤的,這樣導致效率的低下,甚至是“可移植性”問題(虛函數的實現細節是由編譯器決定的,一個類是否包含虛函數會影響類執行個體的記憶體布局)。因此在設計一個類的時候,要考慮它是否可能會被繼承。一個經驗是”只有類包含一個虛函數的時候,才將它的解構函式也聲明為虛的“。
反過來,如果一個類的解構函式是非虛的(或是根本沒有聲明),那麼這通常意味這著這個類並不設計為一個基類。但實際上真正危險的地方是”多態的方式使用“,繼承並不意味著多態,所以如果你確定並不會以多態的方式使用某個類繼承體系,那麼基類沒有虛解構函式也沒有關係。不過我覺得還是盡量避免的好。
條款9:不要在建構函式或解構函式中調用虛函數
有時我們希望基類的建構函式能夠區分是一個基類對象正在構造,還是一個衍生類別對象正在被構造,並以此來執行不同的代碼。我們因此可能會在基類中聲明一個虛函數來封裝這些代碼,然後在建構函式中調用該虛函數。
但實際上C++並不支援我們這麼做,如果在基類的建構函式中調用虛函數,調用到的總是基類的虛函數實現。這是因為此時子類的成員尚未被初始化,調用子類的函數極可能導致未定義的行為。因此C++杜絕了這種可能性。其實在基類的建構函式中,如果查詢該當前對象的動態類型,得到的也必然是基類類型。
對解構函式同樣如此。 我們不僅要注意直接調用虛函數,而且要注意間接調用。
如果我們確實需要第一段所述功能,那麼可以考慮給基類建構函式添加一個參數,衍生類別的初始化類表中通過提供參數來訂製不同的行為。如果這仍然不能滿足需求,那就考慮更改一下設計了。
條款13~15 通過對象管理資源
通過對象來管理資源可以獲得很多好處,充分利用C++對對象的支援: 構造,生存期,析構來保證資源使用的安全性。你可以通過auto_ptr這樣簡單的對象來管理單個的堆對象資源,或許你需要抽象一個來來封裝資源。這樣的類就是資源管理類,具有單一的功能:持有資源。將資源的管理和使用分開可以使代碼具有更好的風格和異常安全性。
我們一般遵循RAII,resouce acquire in initialization 的原則,即在資源管理類的初始化構造中擷取資源,在析構中銷毀資源。
資源管理類的COPY行為需要特別注意,你可能不允許拷貝,或是引用記數的COPY。
某些API可能需要原始資源指標,你可以提供隱式的類型轉換,或是顯示的get函數來提供原始類型。但後者更安全。
條款23: 以non-member non-friend函數代替member函數
如果一個成員函數不需要直接存取類的私人成員,那麼考慮用一個non-member non-friend的方法來代替它。這個問題考慮的是封裝性。封裝性意味著越少的代碼能夠訪問類的私人成員,那麼說明封裝性越好。這點與面相對象的“將資料與操作資料的方法綁定在一起”的設計思想看起來相悖,但其實這個準則本身就是對物件導向的一個誤解。
提供一個none-member none-friend的函數能夠提升我們的封裝性。一般的做法是提供一個名字空間或是一個只包含靜態方法的類**Utility,將這樣的一些功能性方法組織在一起。這個條框與上一個用類來管理資源的思想其實有異曲同工之妙,其根本思想就是“單一功能原則”。 這個類提供基本的功能,至於將一些功能組合成一個方法以方便使用者,就通過另一個類**utility來做吧。
在庫的設計中,比如C++標準庫,這種設計很常見。可以將不同的功能性方法組織到不同的標頭檔、名字空間,以減少編譯依賴。
但在應用程式的設計中,我們偶爾需要一兩個這樣的方法,我認為直接用memeber函數就好了。
條款30:inline函數
如果沒有證據表明某個函數被大量調用導致了效率問題,就不要使用inline。 inline總是伴隨著一些風險的。 像建構函式和解構函式如果聲明為inline幾乎不會是正確的,因為編譯器在建構函式和解構函式中可能插入了一些你所不知的代碼。