《More Effective C++》總結筆記(一)

基礎議題

條款1:仔細區別pointers和references

  • 首先你必須認知一點,沒有所謂的 null reference。
  • 由於reference一定得代表某個對象,C++因此要求references必須有初值。
  • Pointers和references之間的另一個重要差異就是,pointers可以被重新賦值,指向另一個對象,reference卻總是指向(代表)它最初獲得的那個對象。
  • 結論:當你知道你需要指向某個東西,而且絕不會改變指向其他東西,或是當你實現一個操作符而其語法需求無法由pointers達成,你就應該選擇references。任何其他時候,請採用pointers。

條款2:最好使用C++轉型操作符

  • C++提供的4種新的轉型操作符,相比於C風格的轉型,儘管其語法看起來又臭又長。但它提供了嚴謹的意義以及更強的辨識度。
  • 如果你在程序中使用新式轉型法,比較容易被解析(不論是對人類還是對工具而言),編譯器也因此得以診斷轉型錯誤(那是舊式轉型法偵測不到的)。
  • 讓轉型動作既醜陋又不易鍵入(typing),或許未嘗不是件好事。

條款3:絕對不要以多態方式處理數組

  • 因爲基類和派生類大小不一樣,函數傳參時如果是形參是基類數組,實參是派生類的數組,這時在函數內循環遍歷基類數組的結果就是不可預期的。
  • 簡單地說,多態和指針算術不能混用。數組對象幾乎總是會設計指針的算術運算,所以數組和多態不要混用。

條款4:非必要不提供default constructor

class EquipmentPiece {
public:
  EquipmentPiece(int IDNumber = UNSPECIFIED);
  ...
private:
  static const int UNSPECIFIED;  //一個某數數字,意味沒有被指定ID值。
};
  • 這就是一個添加了default constructor的class,這樣我們就可以直接通過
    EquipmentPiece e;
    這樣的語句來產生一個EquipmentPiece的object。雖然方便了使用,但是這幾乎總是會造成class內的其他member functions變得複雜。
  • 添加無意義的default constructor,會影響classes的效率。如果member functions必須測試字段(指的是default constructor被賦了默認值的必備字段)是否真的被初始化了,其調用者便必須爲測試行爲付出時間代價,併爲測試代碼付出空間代價,因爲可執行文件和程序庫都變大了。萬一測試結果爲否定,對應的處理又需要一些空間代價。如果class constructors可以確保對象的所有字段都被正確地初始化,上述所有成本便都可以免除。如果default constructor無法提供這種保證,那麼最好避免讓default constructor出現。雖然這可能會對classes的使用方式帶來某種限制,但同時也帶來一些保證:當你真的使用了這樣的classes,你可以預期它們所產生的對象會被完全地初始化,實際上亦富有效率。

操作符

條款5:對定製的“類型轉換函數”保持警覺

  • 單自變量constructors和隱式類型轉換操作符可以讓自定義類型實參隱式轉換。
  • 重載類型轉換操作符可能會導致錯誤(非預期)的函數被調用。解決辦法就是以功能對等的另一個函數取代重載的類型轉換操作符(參考std::string的c_str)。
  • 將constructors聲明爲explicit,使得編譯器不能因隱式轉換的需要而調用它們,只能顯示的調用。通過這種方式可以消除單自變量constructors引起的隱式轉換。

條款6:區別increment/decrement操作符的前置和後置形式

  • 重載increment前置式和後置式操作符的範式(decrement同理):
// 前置式:累加然後取出(increment and fetch)
UPInt& UPInt::operator++()
{
*this +=1;
return *this;
}
// 後置式:取出然後累加(fetch and increment)
const UPInt UPInt::operator++(int)
{
UPInt oldValue = *this;
++(*this);
return oldValue;
}
  • 爲了使類似i++++這樣的調用不合法,所以重載後置式increment操作符的返回值必須是const的。
  • 後置式increment和decrement操作符的實現應以其前置式兄弟爲基礎。如此一來你就只需維護前置式版本。
  • 因後置式increment(decrement)函數會返回一個臨時對象,所以它天生效率沒有其前置式兄弟高(主要對自定義類型)。

條款7:千萬不要重載&&,||和,操作符

  • C++對於“真假值表達式”採用所謂的“驟死式”評估方式。意思是一旦該表達式的真假值確定,即使表達式中還有部分尚未檢驗,整個評估工作仍告結束。
  • 而如果重載了&&或||操作符則他們變成了函數調用,函數調用則沒辦法實現類似的“驟死式”語法,因爲函數調用必須所有參數都評估完成。而且也無法保證表達式的評估順序,因爲C++語言規範並未明確定義函數調用動作中各參數的評估順序。
  • 逗號操作符也有類似問題。表達式如果內含逗號,那麼逗號左側會先被評估,然後逗號的右側再被評估;最後,整個逗號表達式的結果以逗號右側的值爲代表。而如果重載它,無法模仿這些行爲。

條款8:瞭解各種不同意義的new和delete

  • 如果你希望將對象產生於heap,請使用new operator。它不但分配內存而且爲該對象調用一個constructor。如果你只是打算分配內存,請調用operator new,那就沒有任何constructor會被調用。如果你打算在heap objects產生時自己覺得內存分配方式,請寫一個自己的operator new,並使用new operator,它將會自動調用你所寫的operator new。如果你打算在已分配(並擁有指針)的內存中構造對象,請使用placement new。
  • 總結關係如下:
new <->delete
operator new <-> operator delete //分配、釋放內存
placement new <-> placement delete //在指定內存上構造、析構對象
new[] <-> delete[]
  • operator new調用示範:
void *rawMemroy = operator new(sizeof(string));
  • plcement new示範:
class Widget {
public:
  Widget(int widgetSize);
  ...
};
Widget* constructWidgetInBuffer(void* buffer, int widgetSize)
{
  return new (buffer) Widget(widgetSize);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章