本節書摘來自異步社群出版社《c++程式設計風格(修訂版)》一書中的第2章,第2.7節,作者:【美】tom cargill,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
c++程式設計風格(修訂版)
我們暫時先不去考慮去解決 string 類中的其他問題,而是将注意力轉移到另一個不同的字元 串類。在這個類中,我們避免了大多數的上述問題。我們來分析程式清單 2.3 中的 simplestring 類。 雖然 simplestring 相對于 string 進行了改進,但仍然存在着一些缺陷。
程式清單 2.3 最初的 simplestring 類
與 string 類一樣,simplestring 通過一個字元類型的指針 _string 和一個整數 _length 來表
示字元 串。當 simplestring 對象不為空 時,資料成員 _length 表示的就是字元串的長 度。在 simplestring 對象中可以不包含任何字元串,這是通過一個空的字元類型指針和零長度來表示的。 在 simplestring 中,并不存在字元數組的長度多 1 或少 1、記憶體洩漏等問題,在類的表示和接口 中也不存在不一緻的問題。然而,在 simplestring 中還是存在着一些缺陷。
備援
simplestring 中的資料成員是以一緻的方式來記錄字元串的長度。_length 的類不變性需要包 括兩種情況:如果 _string 是非空指針,那麼 _length 就是字元串的長度;如果 _string 是空指針, 那麼 _length 就應該為零。
在每次改變了 simplestring 對象的狀态之後,_length 的值都需要正确進行計算。然而,這 個長度資訊卻從來沒有用到過。在 simplestring 中,當每次需要字元串的長度時,這個值都将在 strdup() 中重新進行計算。例如,在拷貝構造函數中,雖然 _length 的值是通過對參數進行複制 來得到的,但仍然需要在 strdup() 中調用 strlen() 來計算字元串的長度。
在 simplestring 的實作中,大概有 1/4 的代碼需要用來維護 _length 的正确性。如果這些 代碼并沒有提供很有用的功能,那麼應該從類中去掉它們。我們可以通過去掉 _length 來改進 simplestring。
避免對從不使用的狀态資訊進行計算和存儲。
如果在 simplestring 中需要使用 _length 來避免重新計算字元串的長 度,那麼情況将與 我們在前面讨論過的有所不同。雖然從資訊理論上來看,_length 是備援的,但它可以加快 simplestring 的使用速度,進而能夠提供有用的功能。是以,上面的這條規則不能解釋為“盡可 能少地去存儲資訊”,它的确切含義是,隻有當資訊在後續操作中需要被用到時,才應該存儲。
動态記憶體以及 operator=
在 simplestring 類中定義了兩個指派運算符:
雖然在 simplestring 中沒有了記憶體洩漏,但這兩個運算符函數都可能會過早地删除了記憶體中的字元串。我們在 string::concat() 中已經看到了,在安排删除動态記憶體的時間上需要謹 慎。如果在指派運算兩邊的操作數是同一個 simplestring 對 象,并且調用了 operator=(constsimplestring&),那麼這個表達式的結果将是未定義的。雖然程式員不大可能會顯式地寫出像 x=x 這樣的表達式,但在程式中可能會間接地導緻這種指派運算的發生。如果 a 和 b 碰巧都是引用了同一個 simplestring 對象,那麼 a=b 就等價于是 x=x。無論是何種情況導緻了這種指派運算, 如果在調用 operator= 時,函數參數與 this 指針所指向的是同一塊記憶體,那麼在舊的字元串作為 參數傳遞給 strdup() 之前,就已經被删除了,是以傳回的結果是未定義的。正确的方法是将參數 與 this 的值進行比較,如果二者相等就不進行任何操作。
在定義 operator=時,我們要注意 x=x這種情況。
對于 operator=(const char*) 中的 delete 操作,可能會産生問題的情況是 x=x.string(),或者雖 然以 a=b.string() 的形式出現,但 a 和 b 中的字元指針指向的是同一個位址。在執行 x=s 的指派運 算時,如常量字元類型指針 s 的值等于 x.string(),那麼将會産生和 operator=(const simplestring&) 中同樣的問題。再次指出,無論是上述何種情況,如果在調用 operator= 時,函數的參數指針等 于将被删除的字元串,那麼 strdup() 的結果将是未定義的。對于 string::concat() 函數來說,解決 的方法是将 delete 操作推遲到字元串被複制之後再進行。
注意在上面兩個運算符函數中代碼的相似性。這兩個函數都是先拷貝一個字元串,然後删 除原有的字元串。是以,我們應該在一個運算符函數中調用另一個運算符函數,而不是在兩個不 同的地方進行相同的工作。就像我們在 string 類中用一個帶有預設參數的構造函數來代替最初的 的兩個構造函數一樣,我們沒有理由為同樣的代碼維護兩份拷貝。
最後一個細節問題
對 simplestring 的最後一個改進是關于輔助函數 strdup(),這個函數的作用是對字元串進行複制并存儲在一塊新配置設定的記憶體中。strdup() 中的問題并不在于一緻性,而是在于 simplestring 類中所有調用 strdup() 的地方,調用代碼都遵循着同樣的形式:在每次調用之前,都要進行測 試以保證參數指針是非空的;在從 strdup() 中傳回之後,傳回結果都是儲存在 _string 中。是以, 我們可以對類的實作進行簡化,用一個私有成員函數來代替 strdup(),我們在這個私有函數中首 先對參數指針進行測試,然後對字元串進行拷貝,最後在 _string 中存儲新的指針值。這個修改 是通過将重複的代碼放到一個通用的函數中以消除重複代碼。我們在這裡所應用的規則來自于 kernighan 和 plauger 的著作 [ 第 15 頁 ] :
用一個通用的函數來代替重複的表達式序列。
程式清單 2.4 中給出了改寫之後的 simplestring,其中包含了我們在上面所讨論的修改以及 其他的一些改進。
程式清單 2.4 改寫之後的 simplestring
本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。