當心編譯器生成的隱含成員函式 (轉)

worldblog發表於2008-01-21
當心編譯器生成的隱含成員函式 (轉)[@more@]

  “聽說最近新開了家超市?”

  “是啊,我去過了,什麼都沒有!”

  “真的?什麼都沒有?”

  “我騙你幹什麼!只有些牙膏、牙刷、洗衣粉……真的什麼都沒有。”

  習以為常的東西總是容易被忽略。在生活中我們可以對這些經常見到的東西視而不見,但在程式碼中千萬不要這樣作。

  問題:下面這個類有幾個成員?

  class X

  {

  int value;

  };

  “這麼簡單的問題還問!我當然知道,會為沒有建構函式的類生成一個預設建構函式,為沒有複製建構函式的類生成一個複製建構函式,為沒有解構函式的類生成一個解構函式,為沒有複製賦值運算子的類生成一個複製賦值運算子,所以它有四個隱含成員函式。”

  是的,回答是正確的,但僅限於知道是不行的,必須把它牢牢的記住,直到你一看到這個定義眼前就能浮現出下面的定義:

  class X

  {

  int Value;

  public:

  X();

  X(const X&);

  X& operator = (const X&);

  ~X();

  };

  為什麼這樣說呢?因為儘管在回答上面的問題時大家都能答對,但在實際應用時卻常常把它忽略掉。讓我們看一個例子:

  比如我們要實現一個“智慧指標”(什麼型別的?什麼型別都行,這與本題無關),這,通常都會這樣寫:

  template

  smart_ptr...

  現在我們要為它寫一個複製建構函式和一個複製賦值運算子……等一下。

  我們這個智慧指標是為了在某一應用範圍內代替指標的,我們應該讓它支援指標的一些特性,如自動型別轉換的初始化(複製構造)和賦值。而類别範本和真正的指標是不一樣的,即使一個Derived*是一個Base*,但一個smart_ptr卻不是一個smart_ptr,從smart_ptr到smart_ptr的轉換不是編譯器自動支援的,我們必須為它寫一些程式碼來做這件事。該怎麼做呢?很簡單:

  template

  smart_ptr

  {

  T* ptr_data;

  public:

  T* get_ptr();

  template

  smart_ptr(const smart_ptr & )

  {

  ptr_data = Source.get_ptr();

  //其它處理

  }

  template

  smart_ptr & operator = (const smart_ptr & Source)

  {

  ptr_data = Source.get_ptr();

  //其它處理

  } 

  };

  這樣當我們用smart_ptr初始化smart_ptr的時候,編譯器會自動為smart_ptr類生成一個引數為const smart_ptr& 的建構函式,用smart_ptr::get_ptr()的返回值(型別為Derived*)初始化smart_ptr::ptr_data(型別為Base*),而編譯器會在這裡進行型別檢查,其檢查結果和直接使用指標型別時一樣。現在你鬆了一口氣——一切OK,大功告成……可是你錯了!

  當你寫下這樣一段程式碼時:

  smart_ptr a;

  ...

  smart_ptr b(a);

  smart_ptr c;

  c = a;

  你會發現,它並沒有你的建構函式的賦值運算子。什麼原因呢?原因很簡單:當你沒有寫複製建構函式時,編譯器會為你生成一個隱含的複製建構函式。而複製建構函式的定義是“類X的複製建構函式是指第一個引數為const X&、X&、volatile X&或const volatile X&,並且沒有其它引數或其它引數都有預設值的非模板形式的建構函式”,因此上templatesmart_ptr(const smart_ptr & Source)這個建構函式並不是複製建構函式,由於這個原因,編譯器會為你生成一個隱含的複製建構函式。事實上你的類是這個樣子的:

  template

  smart_ptr

  {

  T* ptr_data;

  public:

  T* get_ptr();

  //以下是編譯器自動生成的三個成員函式

  smart_ptr(const smart_ptr&);

  smart_ptr& operator = (const smart_ptr&);

  ~smart_ptr();

 

  template

  smart_ptr(const smart_ptr & Source)

  {

  ptr_data = Source.get_ptr();

  //其它處理

  }

  template

  smart_ptr & operator = (const smart_ptr & Source)

  {

  ptr_data = Source.get_ptr();

  //其它處理

  } 

  };

  現在我們看的很清楚了:由於smart_ptr(const smart_ptr&);這個函式的存在,在編譯程式碼smart_ptr b(a);編譯器找到了一個型別符合的非模板建構函式,它就會呼叫這個函式而不會去例項化你的模板形式的建構函式。同樣道理,你的模板形式的賦值運算子也不是複製賦值運算子,編譯器同樣會為你生成一個從而阻止了模板的例項化。

  解決辦法很簡單,既然編譯器要呼叫複製建構函式和複製賦值運算子,而隱含的成員又不符合要求,我們就自己寫一個來代替它們:

  template

  smart_ptr

  {

  T* ptr_data;

  public:

  T* get_ptr() const;//返回封裝的指標,可能還需要做一些附加操作,取決於你的智慧指標的設計邏輯。

 

  //自己的複製建構函式和複製賦值運算子:

  smart_ptr(const smart_ptr&)

  {

  ptr_data = Source.get_ptr();

  //其它處理

  }

  smart_ptr& operator = (const smart_ptr&);

  {

  ptr_data = Source.get_ptr();

  //其它處理

  } 

 

  template

  smart_ptr(const smart_ptr & Source)

  {

  ptr_data = Source.get_ptr();

  //其它處理

  }

  template

  smart_ptr & operator = (const smart_ptr & Source)

  {

  ptr_data = Source.get_ptr();

  //其它處理

  } 

  };

  事實上在《C++ STL 中文版》中auto_ptr的實現程式碼中就是這樣實現的。

 

 

  注:在Nicolai M.Josuttis編寫的《C++ 標準程式庫》(我讀的是侯捷和孟巖的譯本)中把這種建構函式稱為“模板形式的複製建構函式”(原譯文為“template形式的copy建構式”)是一種不很嚴密的說法,容易讓人誤解。事實上這種模板形式的建構函式不屬於複製建構函式。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-997932/,如需轉載,請註明出處,否則將追究法律責任。

相關文章