《Effective C++》 筆記

導讀

1. C++對 signature 的定義並不包含函數的返回類型。
2. 將構造函數聲明爲explicit避免隱式轉換
3. 當新對象被定義時,一定會有構造函數被調用,因此Widget w3 = w2; 會調用Widget的copy構造函數,而不是assignment操作符。
4. 可以使用operator * (a, b); 來實現相乘的操作,其他操作符也類似。

1.Accustoming Yourself to C++

Item 1:View C++ as a federation of languages.

1. 內置類型的值傳遞(pass by value)比引用(pass by reference)更高效
2. C++可以認爲是由 C , OO, Template和 STL組成

Item 2: Prefer consts,enums,and inlines to #defines.

1. 定義一個常量字符串,const char* const str = "a string";
2. class專屬常量:如static const int num = 5; 這是一個聲明,需要在類外提供定義const int ClassName::num = 5; (除非這個類型是整數類型,如int char bool, 也有可能編譯不支持省略定義)。
3. 用template inline 函數代替 函數宏。

Item 3: Use const whenever possible

1. const char* const p; 第一個const (*號左邊的,可以放在類型後邊)修飾data, 第二個修飾pointer.
2. STL中,const std::vecotr<int>::iterator iter;代表迭代器不可變,而std::vector<int>::const_iterator cIter代表所指的東西不可變。
3. 對函數的返回值(引用類型)加const使返回的引用不被修改, 這樣的函數可以重載沒有const的函數。當一個const對象調用這個函數時,會調用帶const的那個重載函數。
4. const成員函數不可以改變對象內任何no-static成員變量,除非對變量加上mutable修飾符。
5. 當const 和 non-const 成員函數實現相同時,讓non-const 調用const(使用cast) 來避免代碼重複。

Item 4: Make sure that objects are initialized before they’re used.

1. C++規定,對象的成員變量的初始化應發生在進入構造函數本體之前,在構造函數體內的不叫初始化,而叫賦值。
2. 對於內置類型的初始化,應使用初始化列表。
3. 如果不在進入構造函數本體之前進行初始化,而直接在構造函數體內進行賦值,則會調用一次成員變量的默認構造函數和一次拷貝構造函數,造成了浪費。
4. 初始化順序:父類早於子類。類成員按聲明順序初始化(即使初始化列表的順序不一樣,但應儘量保持和聲明一樣的順序)。
5. 函數內的static變量稱爲local static,其它區域的static變量稱爲non-local static
6. 不同編譯單元(生成一個目標文件的代碼)的non-local static變量的初始順序不確定,當有相互引用時,應使用Singleton模式。
7. 任何一種non-const的static對象,在多線程環境下等待某事發生都會有麻煩。一種處理辦法是在單線程啓動階段,手工調用。(類似的,ios中單例模式,使用dispatch_once來保證)

Constructors, Destructors, and Assignment Operators

Itme 5: Know what functions C++ silently writes and calls.

1.  編譯器默認爲一個class生成(如果被調用的話,後同)copy構造函數、copy assignment 操作符和一個析構函數。如果沒有定義其它構造函數,還會生成一個default構造函數。這些函數都是public 和 inline。
2. c++不允許reference 改指向不同的對象。
3. 生成copy構造函數和copy assignment 時,只是單純的將non-static對象拷貝。如果拷貝不合法,則編譯器不會生成這個函數。

Item 6: Explicitly disallow the use of compiler-generated functions you do not want.

1. 爲駁回上一條中編譯器生的函數,可將其聲明爲private並且不予實現。(c++11中使用 = delete)

Item 7: Declare destructors virtual in polymerphic base classes.

1. 爲多態的基類聲明virtual析構函數。

Item 8: Prevent exceptions from leaving destructors.

1. 不要在析構函數裏拋異常。
2. 如果需要對某個操作函數運行期間拋出異常作出反應,那麼該函數的所在的class應該提供一個普通函數來執行該操作,而不是在這個class的析構函數中。

Item 9: Never call virtual functions during construction or destruction.

1. 因爲父類先於派生類構造,所以父類構造期間virtual 不會起作用。
2. 令derived classes 將必要的構造信息傳遞給base clase 構造函數可以彌補9.1無法實現的功能。

Item 10: Have assignment operators return a reference to *this.

1. 讓operator= 等(還有+=,-=等)操作符重載函數返回 *this的引用,使可以寫成x = y = z這樣的形式。

Item 11: Handle assignment to self in operator=.

1. 通過判斷是否相等處理自我賦值(會有一些性能損失)。
2. 如果不使用1中的方法,在寫operator=時要考慮如果出現自我賦值,會不會導致數據丟失。
3. 使用swap.

Item 12: Copy all parts of an object.

1. Copying 函數(指copy 和 copy assignment)應確保複製所以成員變量和父類成員變量,後者通過調用父類的Copying函數來完成。
2. copy 和 copy assignment 有重複代碼也不應該相互調用,應新建一個private函數來給兩者調用,通常命名爲init.

Resource Management

Item 13: Use objects to manage resources.

1. STL中的auto_ptr就是使用的這個思想,常被稱爲“資源取得時機便是初始化時機”(Resource Acquisition Is Initialization;RAII),RAII可理解爲:“資源在構造期獲得,在析構期釋放”。
2. 使用管理對象的析構函數來確保其擁有的資源被釋放。
3. 當auto_ptr被copy時,將變成null,新的指針將取得資源的所有權。
4. auto_ptr不用於動態分配的數組,因爲它的析構函數裏做的是delete而不是delete []
5. C++11中不再使用auto_ptr.

Item 14: Think carefully about copying behavior in resource-managing classes.

處理RAII對象的拷貝行爲:

1. 禁止拷貝(Item 6中有說)。
2. 使用shared_ptr管理擁有的資源,如果“釋放“動作並不是刪除,可以爲shared_ptr指定刪除器.
3. 深度拷貝。
4. 轉移所有權(像auto_ptr那樣)。

Item 15: Provide access to raw resources in resource-managing classes.

1. auto_ptr 和 shared_ptr 都可以通過get獲取管理的資源的指針。
2. 可使用隱式轉換函數operator OtherClass() const; 獲取其底部資源。(容易出錯,需具體分析)

Item 16: Use the same form in corresponding uses of new and delete.

1. 使用new[] 後,應使用 delete[]來釋放。
2. 不要對數組形式做typedef動作。

Item 17: Storre newd objects in smart pointers in standalone statements.

1. 將初始化智能指針寫成獨立語句。(不過似乎沒有不寫成獨立語句的理由)

4. Designs and Declarations

Item 18: Make interfaces easy to use correctly and hard to use incorrectly.

1. 應將類的接口設計爲不容易被錯誤調用的形式,
2. 使創建資源接口返回一個shared_ptr,強迫調用者使用shared_ptr管理這個指針。
3. 爲防止調用者使用delete刪除2中返回的指針,應爲指針綁定刪除器。

Item 19: Treat class design as type design.

1. Class的設計就是type的設計,應當考慮如下幾個(一部分)問題:
2.  - 新type的對象如何創建和銷燬
3.  - 什麼是type的合法值
4.  - 需要什麼轉換
5.  - 是否需要重載操作符
6.  - 是否使用模板

Item 20: Prefer pass-by-reference-to-const to pass-by-value.

1. 函數參數爲值傳遞方式時,調用的是copy構造函數。
2. 引用方式實際實現是指針,所以當派生被作爲一個基類引用傳遞時,也不會被切割。
3. 適合用值傳遞的類型:內置類型,STL的迭代器和函數對象。

Item 21: Don’t try to return a reference when you must return an object.

1. 不要返回一個局部變量的引用或指針。

Item 22: Declare data members private.

1. 將成員變量聲明爲private,對外提供讀寫函數。

Item 23: Prefer non-member non-frend functions to member functions.

1. 將類的一組操作寫到非成員函數裏邊,並把這些函數放到和類相同的命名空間中,並將不同類型的操作放到不同的頭文件中。這樣做增加了類的封裝性和可擴充性,同時可以降低編譯的依存性。

Item 24: Declare non-member functions when type conversions should apply to all parameters.

1. 重載一個類A的乘操作符時,與類A相乘的是其他類型B,B可以隱式轉換成A,如果需要寫成A*B的形式,則需要將operator*寫成非成員函數。
2. 成員函數的反面應該是非成員函數,而不是friend。

Item 25: Consider support for a non-throwing swap.

1. 可以對類提供一個特化的swap函數來提高效率,一般針對”以指針指向一個對象,內含真正的數據(pimpl)“類型。

Implementations

Item 26: Postpone variable definitions as long as possible

1. 過早的定義變量,如果在使用變量之前拋出異常,則浪費了一次構造和析構的時間。
2. 在循環裏使用的變量,應該定義在循環裏,除非賦值操作的成本低於構造+析構,並且效率敏感。

Item 27: Minimize casting.

1. 使用括號的方式是舊式轉型,在C++應使用cast

2. 但還是要儘量避免使用cast, 尤其dynamic_cast。

Item 28: Avoid returning “handles” to object internals.

1.  避免返回handle 指向對象的內部成份,一旦這樣做就導致handle比其所指對象更長壽,有可能造成懸空。

Item 29: Strive for exception-safe code.

1. 異常安全性函數的要求:不管什麼時候拋出異常,都保證不泄漏任何資源、不允許數據敗壞。
2. 在函數後面加throw() 指定異常的類型,當不是指定類型的異常時,會調用  unexpected handler.  這些特性在c++11中棄用但仍然支持。
3. 函數的異常安全程度:基本保證(數據不敗壞)、強烈保證(在拋出異常時恢復操作前的狀態)、不拋異常。
4. 使用copy-and-swap來提供強烈保證,既修改再成功後再一起替換。

Item 30: Understand the ins and outs of inlining.

1. 編譯器的優化機制通常被設計用來優化那些”不含函數調用“的代碼。
2. 太複雜的函數和virtual函數的inline申請會落空。
3. 函數指針調用會忽略inline聲明。
4. inline函數可能會無法調試。
5. 謹慎使用inline函數,限制在小型、被頻繁調用的函數身上。

Item 31: Minimize compilation dependencies between files.

1. 使用class的聲明式class xxx;而不是#include 可以降低文件的依存性。
2. 考慮Handle classes 和 Interface clases。(不太理解其優點)

Item 32: Make sure public inheritance models “is-a”.

1. public inheritance 意味着“is-a”的關係。
2. 防止無效的代碼通過編譯:在編譯期拒絕“企鵝飛行”的設計。

Item 33: Avoid hiding inherited names.

1. 局部變量只要名稱和全局變量相同就會掩蓋,不管類型一不一樣。
2. 派生類的函數會覆蓋基類中所有同名的函數,與類型無關,但可以使用using BaseClass::func;使用它,讓基類的所有同名函數在派生類都可以見。

Item 34: Differentiate between inheritance of interface and inheritance of implementation.

1. pure virtual 是可以提供定義的,但調用時要指出其class名稱。可以用來爲virtual函數提供缺省實現。
2. 聲明pure virtual 函數的目的是爲了讓derived classes 只繼承函數接口。
3. 聲明simple virtual函數的目的,是讓derived classes 繼承該函數的接口和缺省實現。
4. 聲明non-virtual函數的目的是爲了令derivaed classes 繼承函數的接口及一份強制性實現(不應該被重新定義)。

Item 35: Consider alternatives to virtual functions.

1. 使用NVI和Strategy手法來替代virtual。(Strategy 的具體優點不太理解)
2. C++中允許private爲virtual,JAVA中禁止。

Item 36: Never redefine an inherited non-virtual functions.

1. 不重新定義繼承而來的non-virtual函數。(似乎34已經說明了這個問題)

Item 37: Never redefine a function’s inherited default parameter value.

1. 當一個virtual函數有默認參數值時,繼承它時,不能更改其默認參數值。因爲默認參數值是靜態綁定的,也就是會一隻使用base class 的默認參數。一個替代方案就是使用35的NVI手法。

Item 38: Model “has-a” or “is-implemented-in-terms-of” through composition.

1. 軟件處理的對象分爲兩個領域,應用域:對應世界中事物,如人、汽車等。實現域:實現細節上的人工製品,如緩衝區、互斥器等。
2. 應用域使用has-a關係,實現域則是is-impl-in-terms-of (185頁).

Item 39: Use private inheritance judiciously.

1. 使用protect 和 private繼承時,不會自動將一個derived class 對象轉換成base class對象。
2. private繼承意味着複合中的is-impl-in-terms-of,只是一種實現,在設計上沒有特殊含義。並且應儘量使用複合來實現這種關係,除非和protect或private成員扯上關係時。
3. 特殊情況:當base class不佔用空間時,而derived class 又對空間敏感,且是單繼承時,應使用private繼承實現is-impl-in-terms-of 關係。

Item 40: Use multiple inheritance judiciously.

1. 當多繼承的base classes 又同時繼承自一個base class時,既A繼承自B和C,B和C繼承自D, 默認情況下,A擁有兩份D的數據成員。如果B和C使用virtual繼承D,則A不會有兩份D的數據成員。
2. virtual base class應該儘量不帶數據。
3. 多重繼承應用於:public繼承一個interface class ,private繼承某個協助實現的class
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章