《More Effective C++》要點總結


基礎內容

1.指針與引用的區別

  任何情況下都不能使用指向空值的引用,使用時必須初始化。這使得使用引用時的效率比使用指針要高,因為在使用之前不需要測試它的合法性。

  引用總是指向在初始化時指定的對象,以后不能改變。

  重載某個操作符時,應該使用引用。

2.盡量使用C++風格的類型轉換

  static_cast, const_cast, dynamic_cast, 和 reinterpret_cast。 

  (double)number,改成使用static_cast<double>(number).

3.不要對數組使用多態

  使用基類數組指針指向子類數組時,計算出的對象大小會有問題。

4.避免無用的缺省構造函數

  有時無法確定對象是否被正確的初始化(某些參數的值是否有意義)

運算符

5.謹慎定義類型轉換函數

  編譯器允許的轉換:單參數構造函數(只有一個參數或除第一個外都有缺省值)和隱式類型轉換運算符(operator 關鍵字,如 operator double() const;)。

  編譯器在某些情況下會調用非用戶期望的隱式類型轉換。解決方法是不使用語法關鍵字的等同函數來替代轉換運算符。如用asDoule函數代替operator double函數。

  使用關鍵字explicit可以避免隱式轉換。

6.自增、自減操作符前綴形式和后綴形式的區別

class UPInt {
public:
    UPInt& operator++():              //++前綴
    const UPInt operator++(int);    //++后綴
    UPInt& operator--();                //--前綴
    const UPInt operator--(int);      //--后綴
    UPInt& operator+=(int);          //+=操作符
}

UPInt i;
++i;   //調用i.operator++();    
i++;   //調用i.operator++(0);
--i;   //調用i.operator--();
i--;   //調用i.operator--(0);

7.不要重載 && || 或 , 

    重載之后相當於函數調用,用函數調用法替代了短路求值法。

    if (expression1 && expression2) 變成了

    if (expression1.operator&&(expression2))  或者 if (operator&&(expression1, expression2)),

    后面的是函數調用,需要計算出所有參數,而沒有采取短路計算法。

8.理解各種不同含義的new和delete

    string *ps = new string("Memory Management");  這里使用的new是new操作符,像sizeof一樣是語言內置的,不能改變其含義。其完成的功能分兩部分:a.分配足夠的內存,b.調用構造函數初始化內存對象。

    可以改變如何為對象分配內容。new 操作符調用 operator new 來完成必需的內存分配,可以重寫或重載這個函數來改變它的行為,通常聲明為 void* operator new(size_t size); 可以增加額外的參數,但第一個參數必須是size_t。

    operator new的職責只是分配內存。

    placement new。如果需要在一些已經分配但尚未處理的內存中構造對象需要使用placement new,例如

class Widget {
public:
    Widget(int widgetSize);    
};

//函數返回一個Widget對象指針,對象在函數參數buffer里分配。
//適用於使用共享內存或memory-mapped I/O時
Widget* contructWidgetInBuffer(void *buffer, int widgetSize)
{
    return new (buffer) Widget(widgetSize);    
}

new與delete應該配對使用。使用placement new初始化的對象不應該使用delete,因為是在其他地方分配的內存。

異常

9.使用析構函數防止資源泄露

    如果在堆上創建了局部對象,運行過程中出現異常后對象的析構方法不會調用。可以使用scoped_ptr解決。

10.在構造函數中防止資源泄露

    如果構造函數會拋出異常,需要在拋出異常時清理創建出的內容,然后重新拋出異常將其繼續傳遞。

11.禁止異常信息傳遞到析構函數外

    原因1:能夠在異常傳遞的堆棧輾轉開解(stack-unwinding)的過程,防止terminate被調用。

    原因2:能夠幫助析構函數完成起作用。防止不完全的析構。

12.理解“拋出一個異常”與“傳遞一個參數”或“調用一個虛函數”間的差異

    拋出異常時傳遞的是對象的拷貝,即使原始的對象不會被釋放。所以拋出異常比參數傳遞要慢。

    在catch中重新拋出異常時,throw; 會將捕獲的異常傳遞出去,而 throw e; 會拷貝異常。

   catch接收的參數不支持隱式類型轉換(如int to double)。不過在catch子句中進行異常匹配是可接受兩種類型轉換:a.繼承類與基類的轉換。b.允許從一個類型化指針轉變成無類型指針(const void*指針的catch子句能捕獲任何類型的指針類型異常)。

13.通過引用捕獲異常

14.審慎使用異常規格

15.了解異常處理的系統開銷

效率

16.牢記80-20准則

17.考慮使用lazy evaluation(懶惰計算法)

    引用計數

    區別對待讀取和寫入

    Lazy Fetching(懶惰提取)

    Lazy Expression Evaluation(懶惰表達式計算)

18.分期攤還期望的計算

19.理解臨時對象的來源

    C++會為常量引用產生臨時對象,不會為非常量引用產生臨時對象。

 20.協助完成返回值優化

  返回constructor argument而不是直接返回對象,編譯器會對調用的地方進行優化從而避免臨時對象產生,例如

const Rational operator*(const Rational& lhs, const Rational& rhs)
{
    return Rational(lhs.numerator() * rhs.numerator(),
                           lhs.denominator() * rhs.denominator());  
}

21.通過重載避免隱式類型轉換

  每一個重載的operator必須帶有一個用戶定義的類型參數。

22.考慮使用運算符的賦值形式(op=)取代其單獨形式(op)

  總的來說operator的賦值形式比單獨形式效率更高,因為單獨形式要返回一個新對象

  提供operator的賦值形式的同時也要提供其標准形式

23.考慮變更程序庫

24.理解虛擬函數、多繼承、虛基類和RTTI所需的代價

  虛函數使對象變得更大,並且不能使用內聯。

技巧

25.將構造函數和非成員函數虛擬化

  虛擬構造函數是指能夠根據輸入給它的數據的不同而建立不同類型的對象。

26.限制某個類所能產生的對象數量

  類中的靜態對象總是被構造,即使不使用該對象。函數中的靜態對象只有第一次執行函數時才會建立,但每次調用該函數時都需要檢查是否需要建立對象。

  帶有private構造函數的類不能作為基類,也不能嵌入到其他對象中。

27.要求或禁止在堆中產生對象

  將類的的析構函數聲明為私有,並提供額外的析構方法可以阻止在棧上創建對象。

28.靈巧指針

  使用operator void*();來實現指針是否為NULL的判斷會引起隱式類型轉換的問題,某些情況下會自動轉換為void*。可以使用operator bool代替。

  可以使用bool operator!() const;來實現指針是否為空的判斷,為空時返回true。

  除非有一個讓人非常信服的原因,否則絕對不要提供到dumb指針(非智能指針)的隱式類型轉換操作符 operator T*();。

29.引用計數

30.代理類

31.讓函數根據一個以上的對象來決定怎么虛擬

雜項

32.在未來時態下開發程序

  通過C++本身來實現某些限制,而不是在文檔中說明。

  基於最小驚訝原則。

  你需要虛析構函數,只要有人 delete 一個實際值向 D 的 B *。

33.將非尾端類設計為抽象類

34.如何在同一程序中混合使用C++和C

35.讓自己習慣使用標准C++語言


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM