讀書筆記 effective c++ Item 22 將數據成員聲明成private。本站提示廣大學習愛好者:(讀書筆記 effective c++ Item 22 將數據成員聲明成private)文章只能為提供參考,不一定能成為您想要的結果。以下是讀書筆記 effective c++ Item 22 將數據成員聲明成private正文
我們首先看一下為什麼數據成員不應該是public的,然後我們將會看到應用在public數據成員上的論證同樣適用於protected成員。最後夠得出結論:數據成員應該是private的。
1. 為什麼數據成員不能是public的?為什麼數據成員不能夠是public的?
2.1 一致性讓我們從句法的一致性開始(Item 18)。如果數據成員不是Public的,那麼客戶訪問對象的唯一方法就是通過成員函數。如果所有的公共接口都是函數,客戶就不必記住訪問一個類的成員時是否使用括號了。這方便了客戶的使用。
2.2 對數據成員訪問的精確控制如果一致性沒有讓你信服,那麼使用函數可以使你對數據成員的訪問有更加精確的控制呢?如果你將數據成員聲明成public的,每個人對其都有讀寫權限,但是如果你使用函數來對值進行獲取(get)或者設置(set),你就可以實現不可訪問(no access),只讀訪問(read only)和讀寫(read-write)訪問。如果你需要,你甚至可以實現只寫(write-only)訪問:
1 class AccessLevels { 2 3 public: 4 5 ... 6 7 int getReadOnly() const { return readOnly; } 8 9 void setReadWrite(int value) { readWrite = value; } 10 11 int getReadWrite() const { return readWrite; } 12 13 void setWriteOnly(int value) { writeOnly = value; } 14 15 private: 16 17 int noAccess; // no access to this int 18 19 int readOnly; // read-only access to this int 20 21 int readWrite; // read-write access to this int 22 23 int writeOnly; // write-only access to this int 24 25 };
這種細粒度的訪問控制是很重要的,因為許多數據成員應該被隱藏起來。很少情況下需要所有的數據成員都有一個getter和一個setter。
2.3 封裝仍然沒有說服力?該是使出殺手锏的時候了:封裝。如果你通過一個函數來實現對一個數據成員的訪問,日後你可能會用計算來替代數據成員,使用你的類的任何客戶不會覺察出類的變化。
舉個例子,假設你在實現一個應用,自動化設備使用這個應用來記錄通過車輛的速度。當每輛車通過的時候,速度被計算出來,然後將結果保存在一個數據集中,這個數據集記錄了迄今為止收集的所有速度數據:
1 class SpeedDataCollection { 2 3 ... 4 5 public: 6 7 void addValue(int speed); // add a new data value 8 9 double averageSoFar() const; // return average speed 10 11 ... 12 13 };
現在考慮成員函數averageSoFar的實現。一種實現的方法是在類中定義一個數據成員,用來表示迄今為止所有速度數據的平均值。當averageSoFar被調用的時候,它只是返回這個數據成員的值。另外一種方法是在每次調用averageSoFar的時候重新計算平均值,這可以通過檢查數據集中的每個數據值來做到。
第一種方法使得每個SpeedDataCollection對象變大,因為你必須為保存平均速度,累積總量以及數據點數量的數據成員分配空間。然而,averageSoFar可以被很高效的實現出來;它只是一個返回平均速度的內聯函數(見Item 30)。相反,在請求的時候才計算平均值會使得averageSoFar運行非常緩慢,但是每個SpeedDataCollection對象會比較小。
誰能確定哪個才是更好的呢?在一台內存吃緊的機器上,並且應用中對平均值的需要不是很頻繁,每次計算平均值可能會是一個更好的選擇。在一個對平均值需求頻繁的應用中,速度很重要,但內存充足,你可能更喜歡將平均速度保存為數據成員。這裡的重要一點是通過一個成員函數來訪問平均值(也就是將其封裝起來),你可以在這些不同實現之間來回切換,客戶端至多只需要重新編譯就可以了。(通過Item31中描述的技術,你甚至可以不用重新編譯)
將數據成員隱藏在函數接口後邊可以靈活的提供不同種類的實現。舉個例子,它可以使下面這些實現變得很簡單:當數據成員被讀或者寫的時候通知其它對象;驗證類的不變性和函數的先置和後置條件;在多線程環境中執行同步等等。從其它語言(像Delphi和C#)轉到C++的程序員將會識別出來C++的這種功能同其它語言中的“屬性”是等同的,但是需要額外加一對括號。
封裝比它起初看起來要重要。如果你對客戶隱藏你的數據成員(也就是封裝它們),你就能夠確保類能一直維持不變性,因為只有成員函數能夠影響它們。進一步來說,你保留了日後對實現決策進行變動的權利。如果你沒有將這些決策隱藏起來,你將會很快發現即使你擁有一個類的源碼,但是你修改public成員的能力是及其受限的,因為如果修改public成員,太多的客戶代碼會被破壞。Public意味這沒有封裝,更實際的講,未封裝意味這不能變化,特別對被廣泛使用的類更是如此。因此對廣泛使用的類最需要進行封裝,因為它們最能受益於將一個實現替換為一個更好的實現。
2. 為什麼數據成員不能是protected的?上面的論證對於protected數據成員來說是類似的。事實上,它們是完全相同的,雖然一開始看上去不是這樣。在論證數據成員不能為public時,句法一致性和細粒度訪問控制這兩個原因同樣適用於protected成員,但是封裝呢?protected數據成員不是有比public數據成員更好的封裝性麼?令人感到吃驚的回答是,它們不是。
Item 23解釋了封裝性同一些東西發生變化引起的代碼可能被破壞的數量成反比。一個數據成員的封裝型,同數據成員發生變化引起的代碼可能被破壞的數量成反比,舉個例子,如果數據成員從類中移除。(可能被一個計算代替,正如在averageSoFar中實現的)。
假設我們有一個public數據成員,我們將其刪除。有多少代碼會被破壞?所有使用它的客戶代碼將被破壞,一般情況下這應該是個未知的數量。Public數據成員因此完全沒有被封裝。但是假設我們有一個protected數據成員,我們將其刪除。現在會有多少代碼被破壞呢?所有使用它的派生類,同樣的,這也是未知數量的代碼。Protected數據成員同public數據成員一樣也沒有被封裝,因為在兩種情況中,如果數據成員被修改,都有未知數量的客戶代碼會被破壞。這是違反直觀的,但是一個經驗豐富的庫實現人員會告訴你,這就是真的。一旦你將一個數據成員聲明成public或者protected並且客戶開始使用它,很難改變數據成員的任何東西。因為一旦修改了,太多的代碼會被重新實現,重新測試,重新編輯文檔,重新編譯。從封裝的角度來說,真的只有兩種訪問級別:private(提供了封裝)和其它的(沒有提供封裝)。
3. 總結