閱讀《Effective C++》系列

《Effective C++》條款07:爲多態基類聲明virtual析構函數

這樣作主要是爲了防止內存泄漏,見我hexo博客。git

C++的虛析構函數程序員

《Effective C++》條款11:在operator=中處理「自我賦值」

「自我賦值」發生在對象賦值給本身時:github

class Widget { ... }
Widget w;
...
w=w;
a[i]=a[j]; //潛在的自我賦值,若是i和j有相同的值
*px=*py;   //潛在的自我賦值,若是px和py剛好指向同一個東西

若是遵循條款13和條款14的忠告,你會運用對象來管理資源,並且你能夠肯定所謂「資源管理對象」在copy發生時有正確的舉措。這種狀況下你的賦值操做符或許是「自我賦值安全的」(self-assignment-safe),不須要額外操心。然而若是你嘗試自行管理資源(若是你打算寫一個用於資源管理的class就得這樣作),可能會掉進「在中止使用資源以前意外釋放了它」的陷阱。安全

其實從上面例子來看,彷佛沒有太大的問題,但假設你簡歷一個class來保存一個指針指向一塊動態分配的位圖(bitmap)hexo

class Bitmap { ... }
class Widget {
  ...
private:
  Bitmap* pb;  // 指針,指向一個從heap分配而獲得的對象
};

對於每次賦值,咱們要考慮到資源管理,便可能會寫出以下的代碼:函數

Widget&
Widget::operator=(const Widget& rhs) //一份不安全的operator=實現版本
{
  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
}

這裏自我賦值的問題是,operator=函數內的*this(賦值的目的端)和rhs有多是同一個對象。果然如此,delete就不僅是銷燬當前對象的bitmap,它也銷燬rhs的bitmap。測試

能夠經過「證同測試」達到「自我賦值」的檢驗目的:this

Widget& Widget::operator=(const Widget& rhs)
{
  if(this == &rhs) return *this;
  delete pb;
  pb=new Bitmap(*rhs.pb);
  return *this;
}

然而,這個版本不具有「異常安全性」,考慮在「new Bitmap」致使異常,它將獲得一個指針指向一塊被刪除的Bitmap。spa

解決辦法:設計

  • 在複製pb所指東西以前不刪除pb:
Widget& Widget::operator=(const Widget& rhs)
{
  Bitmap* pOrig=pb;
  pb=new Bitmap(*rhs.pb);
  delete pOrig;
  return *this;
}
  • 使用copy and swap技術
class Widget {
...
void swap(Widget& rhs);
...
};
Widget& Widget::operator=(const Widget& rhs)
{
  Widget temp(rhs);
  swap(temp);
  return *this;
}
  • 進階版:利用傳值方式,並考慮傳值方式形成的副本
Widget& Widget::operator=(Widget rhs)
{
  swap(rhs);
  return *this;
}

《Effective C++》條款25:考慮寫一個不拋出異常的swap函數

swap動做的典型實現:

namespace std {
  template<typename T>
  void swap(T& a, T& b)
  {
      T temp(a);
      a=b;
      b=temp;
   }
}
  • 當std::swap對你的類型效率不高時,提供一個swap成員函數,並肯定這個函數不拋出異常。

  • 若是你提供一個member swap,也該提供一個non-member swap用來調用前者,對於classes(而非templates),也請特化 std::swap。

  • 調用swap時應針對std::swap使用using聲明式,而後調用swap而且不帶任何「命名空間資格修飾」

  • 爲「用戶定義類型」進行std templates全特化是好的,但千萬不要嘗試在std內加入某些對std而言全新的東西

《More Effective C++》條款01:指針與引用的區別

指針與引用看上去徹底不一樣(指針用操做符’*’和’->’,引用使用操做符’.’),可是它們彷佛有相同的功能。指針與引用都是讓你間接引用其餘對象。你如何決定在何時使用指針,在何時使用引用呢?

  • 任何狀況下不能用指向空值的引用,而指針沒這樣的限制。

    不存在指向空值的引用這個事實意味着使用引用的代碼效率比使用指針的要高。由於在使用引用以前不須要測試它的合法性。

  • 如下狀況下你應該使用指針,一是你考慮到存在不指向任何對象的可能(在這種狀況下,你可以設置指針爲空),二是你須要可以在不一樣的時刻指向不一樣的對象(在這種狀況下,你能改變指針的指向)。

  • 若是老是指向一個對象而且一旦指向一個對象後就不會改變指向,那麼你應該使用引用。當你重載某個操做符時,你也應該使用引用。

《More Effective C++》條款06:自增(increment)、自減(decrement)操做符前綴形式與後綴形式的區別

直接從代碼層面說明區別,定義一個類

class UPInt { // "unlimited precision int"
  public:
   UPInt& operator++(); // ++ 前綴
   const UPInt operator++(int); // ++ 後綴

   UPInt& operator--(); // -- 前綴
   const UPInt operator--(int); // -- 後綴

   UPInt& operator+=(int); // += 操做符,UPInts
                        // 與ints 相運算
   ...
  };

前綴操做的自增是用相似如下的代碼:

// 前綴形式:增長而後取回值
  UPInt& UPInt::operator++()
  {
   *this += 1; // 增長
   return *this; // 取回值
  }

然後綴形式,則是如此:

const UPInt UPInt::operator++(int)
  {
   UPInt oldValue = *this; // 取回值
   ++(*this); // 增長
   return oldValue; // 返回被取回的值
  }

後綴的會有個臨時對象的產生,效率高低比較明瞭。

《More Effective C++》條款08:理解各類不一樣含義的new與delete

通常狀況下,new operator=先operator new + 後 placement new,前者用於分配存儲空間,後者用於調用構造函數初始化所分配的內存。

《More Effective C++》條款19:理解臨時對象的來源

在C++中真正的臨時對象是看不見的,它們不出如今你的源代碼中。創建一個沒有命名的非堆(non-heap)對象會產生臨時對象。這種未命名的對象一般在兩種條件下產生:爲了使函數成功調用而進行隱式類型轉換和函數返回對象時。

臨時對象是有開銷的,因此你應該儘量地去除它們,然而更重要的是訓練本身尋找可能創建臨時對象的地方

主要有如下兩個地方:

  • 任什麼時候候只要見到常量引用(reference-to-const)參數,就存在創建臨時對象而綁定在參數上的可能性

  • 任什麼時候候只要見到函數返回對象,就會有一個臨時對象被創建(之後被釋放)

《More Effective C++》條款26:限制某個類所能產生對象的數量

據我所知,C++控制類的一些trick主要包括構造函數設置爲private+設置static函數調用它們設置友元函數/類

  • 容許創建零個或一個對象

阻止創建某個類的對象,最容易的方法就是把該類的構造函數聲明在類的private域

class CantBeInstantiated {
  private:
   CantBeInstantiated();
   CantBeInstantiated(const CantBeInstantiated&);
   ...
  };
  • 這樣的限制太強了一些,用友元放鬆限制:
class PrintJob; // forward 聲明
                // 參見Effective C++條款34

  class Printer {
  public:
   void submitJob(const PrintJob& job);
   void reset();
   void performSelfTest();
   ...

  friend Printer& thePrinter();

  private:
   Printer();
   Printer(const Printer& rhs);
   ...
  };

  Printer& thePrinter()
  {
   static Printer p; // 單個打印機對象
   return p;
  }
  • 用靜態函數解除限制
class Printer {
  public:
   static Printer& thePrinter();
   ...

  private:
   Printer();
   Printer(const Printer& rhs);
   ...
  };

  Printer& Printer::thePrinter()
  {
   static Printer p;
   return p;
  }
  • 經過靜態變量來控制類的數量
class Printer {
  public:
   class TooManyObjects{}; // 當須要的對象過多時
                        // 就使用這個異常類 
   Printer();
   ~Printer();
   ...

  private:
   static size_t numObjects;
   Printer(const Printer& rhs); // 這裏只能有一個printer,
                             // 因此不容許拷貝
  };                             // (參見Effective C++ 條款27)
 // Obligatory definition of the class static
  size_t Printer::numObjects = 0;

  Printer::Printer()
  {
   if (numObjects >= 1) {
    throw TooManyObjects();
   }

   繼續運行正常的構造函數;

   ++numObjects;
  }

  Printer::~Printer()
  {
   進行正常的析構函數處理;

   --numObjects;
  }

《More Effective C++》條款27:要求或禁止在堆中產生對象

系統自動分配的內存是棧內存,是由系統自動分配、釋放。程序員經過new或malloc操做開闢的內存,是堆內存,由程序員經過代碼進行分配、釋放

有以上的條件,咱們知道,禁止在堆中產生對象,即限制new的功能;要求只在堆中產生對象,即限制系統對象的實例化

  • 要求只在堆中產生對象

讓咱們先從必須在堆中創建對象開始提及。爲了執行這種限制,你必須找到一種方法禁止以調用「new」之外的其它手段創建對象。這很容易作到。非堆對象(non-heap object)在定義它的地方被自動構造,在生存時間結束時自動被釋放,因此只要禁止使用隱式的構造函數和析構函數,就能夠實現這種限制。

把這些調用變得不合法的一種最直接的方法是把構造函數和析構函數聲明爲private。這樣作反作用太大。沒有理由讓這兩個函數都是private。最好讓析構函數成爲private,讓構造函數成爲public。處理過程與條款26類似,你能夠引進一個專用的僞析構函數,用來訪問真正的析構函數。客戶端調用僞析構函數釋放他們創建的對象。

class UPNumber {
  public:
   UPNumber();
   UPNumber(int initValue);
   UPNumber(double initValue);
   UPNumber(const UPNumber& rhs);

   // 僞析構函數 (一個const 成員函數, 由於
   // 即便是const對象也能被釋放。)
   void destroy() const { delete this; }
   ...

  private:
   ~UPNumber();
  };
  • 禁止在堆中產生對象
class UPNumber {
  private:
   static void *operator new(size_t size);
   static void operator delete(void *ptr);
   ...
  };

《More Effective C++》條款28:靈巧(smart)指針

靈巧指針是一種外觀和行爲都被設計成與內建指針相相似的對象,不過它能提供更多的功能。它們有許多應用的領域,包括資源管理(參見條款九、十、25和31)和重複代碼任務的自動化(參見條款17和29)

當你使用靈巧指針替代C++的內建指針(也就是dumb pointer),你就能控制下面這些方面的指針的行爲:

  構造和析構。你能夠決定創建靈巧指針時應該怎麼作。一般賦給靈巧指針缺省值0,避免出現使人頭疼的未初始化的指針。當指向某一對象的最後一個靈巧指針被釋放時,一些靈巧指針負責刪除它們指向的對象。這樣作對防止資源泄漏頗有幫助。

  拷貝和賦值。你能對拷貝靈巧指針或設計靈巧指針的賦值操做進行控制。對於一些類型的靈巧指針來講,指望的行爲是自動拷貝它們所指向的對象或用對這些對象進行賦值操做,也就是進行deep copy(深層拷貝)。對於其它的一些靈巧指針來講,僅僅拷貝指針自己或對指針進行賦值操做。還有一部分類型的靈巧指針根本就不容許這些操做。不管你認爲應該如何去作,靈巧指針始終受你的控制。

大多數靈巧指針模板看起來都象這樣:

template<class T> //靈巧指針對象模板
  class SmartPtr { 
  public:
   SmartPtr(T* realPtr = 0); // 創建一個靈巧指針
                          // 指向dumb pointer所指的
                          // 對象。未初始化的指針
                          // 缺省值爲0(null)

   SmartPtr(const SmartPtr& rhs); // 拷貝一個靈巧指針

   ~SmartPtr(); // 釋放靈巧指針

   // make an assignment to a smart ptr
   SmartPtr& operator=(const SmartPtr& rhs);

   T* operator->() const; // dereference一個靈巧指針
                       // 以訪問所指對象的成員

   T& operator*() const; // dereference 靈巧指針

  private:
   T *pointee; // 靈巧指針所指的對象
  };
相關文章
相關標籤/搜索