Effective C++ - 設計與宣告
author: lunar
date: Mon 14 Sep 2020 07:12:16 PM CST
5. 設計與宣告
條款20: Prefer pass-by-reference-to-const to pass-by-value
預設情況下C++會以傳值形式作為函式引數, 但是這樣會帶來鉅額的建構函式和解構函式的開銷.
所以在確定被呼叫函式不需要對引數進行修改時, 應該進行引用傳值. 同時為了確保傳參不會被改變, 應該使用const進行修飾.
void func(const Student& s);
不要使用傳值方式的另一個原因是傳參對於類資訊的切割, 比如你寫的一個函式接受某個base class作為引數, 那麼其子類按道理來說都可以作為引數傳入. 但是其子類所有的特徵化資訊都會被切除. 所以如果你需要在這個函式內呼叫的函式在子類有過載的話, 可能會失敗.
只有通過pass-by-reference-to-const才能解決這個問題.
以上規則並不適用於內建型別, 以及STL的迭代器和函式物件. 對它們而言, pass-by-value更加適合.
條款21: Don't try to return a reference when you must return an object.
最重要的一點是在函式內建立的local物件位於棧區, 在函式退出後就會被銷燬. 如果對該引用進行操作, 將會帶來不明確行為.
如果你說我可以通過new
在堆區建立一個不會被自動銷燬的物件, 那將會是一個更加糟糕的做法.
因為這表示所消耗的資源需要自己回收, 這就使得使用者在使用函式之後還要記得回收資源, 違背了介面設計的簡潔易用的原則.
更何況, 很多時候使用者在使用介面時可能是將結果直接作為式中的一個元素進行運算, 都不會找個變數進行承接, 更別提進行回收了, 這絕對會造成資源洩露.
如果將結果的reference指向一個static靜態物件, 也有很多問題. 因為static物件是全域性唯一的, 首先就帶來了執行緒安全問題. 其次, static物件不會被多次建立, 導致多次呼叫函式返回的結果都指向同一個結果, 這可能並不是呼叫者希望看到的結果.
所以, 儘管返回一個新物件會帶來構造和析構成本, 但是與這些問題比起來是值得的.
條款22: 將成員變數宣告為private
首先是一致性問題, 使用者不需要考慮到底是直接調取成員變數還是通過成員函式來呼叫. 因為使用者可以接觸到的只有成員函式.
其次是安全性考慮. 將成員變數隱藏在函式介面的背後, 可以為"所有可能的實現"提供彈性. 比如你要在使用者每一次呼叫某個成員變數時列印一下日誌, 如果你是直接暴露成員變數給客戶, 那麼這個操作很難實現. 如果是暴露介面的話, 那麼可以加入很多自己希望的操作.
條款23: Prefer non-member non-friend functions to member functions.
假設網頁瀏覽器類中存在一些成員函式, 分別用來清楚網頁中的一些資料:
class WebBrowser {
public:
...
void clearCache();
void clearCookie();
void clearHistory();
...
};
有的使用者可能想一次執行所有操作:
class WebBrowser {
public:
...
void clearEverything();
...
};
還有一種做法是另外設立一個函式, 依次執行該類的所有關於清楚資料的函式:
void clearEverything(WebBrowser& web) {
web.clearCache();
web.clearCookie();
web.clearHistory();
}
那麼, 哪種做法更好呢?
相信很多人都會認為第一種封裝性更好, 應該選第一種.
可事實是第二種做法帶來的封裝性更好. 封裝的本質是為了讓我們改變事務而影響更少的客戶. 現在增加了clearEverything
成員函式之後, 訪問資料的函式增多了, 所以對於資料的封裝性反而減少了.
所以, 並不是把所有的東西都放在類裡面就叫封裝性好.
所以你面臨在成員函式和非成員函式之間做出選擇的話, 就應該儘量選擇非成員函式. 因為非成員函式不會帶來任何對於成員變數的訪問的風險.
當然, 對於非友元函式和友元函式的抉擇一樣. 友元函式和成員函式一致, 兩者對於類的封裝性的衝擊也是一致的.
在C++中, 比較自然的做法是讓clearEverything
成為一個普通函式並與WebBrowser
位於同一個namespace中.
這樣使用者可以任意在namespace中新增便利函式而不影響任何封裝性.
條款24: Declare non-member functions when type conversions should apply to all parameters.
因為隱式型別轉換隻會作用到運算子右邊的引數, 左邊的引數如果也需要進行型別轉換的話就無法通過編譯.