Guru of the Week 條款10:記憶體管理(下篇) (轉)

worldblog發表於2007-12-10
Guru of the Week 條款10:記憶體管理(下篇) (轉)[@more@] 

GotW #10 Memory Management - Part II

著者:Herb Sutter 

翻譯:kingofark

[宣告]:本文內容取自網站上的Guru of the Week欄目,其著作權歸原著者本人所有。譯者kingofark在未經原著者本人同意的情況下翻譯本文。本翻譯內容僅供自學和參考用,請所有閱讀過本文的人不要擅自轉載、傳播本翻譯內容;本翻譯內容的人請在閱讀瀏覽後,立即刪除其。譯者kingofark對違反上述兩條原則的人不負任何責任。特此宣告。

Revision 1.0

Guru of the Week 條款10:管理(下篇):namespace prefix = o ns = "urn:schemas--com::office" />

 

難度:6 / 10

 

(你在考慮對某個類實現你自己特定的記憶體管理方案嗎?甚或是想幹脆替換掉C++的全域性運算子newdelete?先試試下面這個問題再說吧!)

 

 

[問題]

 

下面的程式碼摘自某個,這個程式有一些類使用它們自己的記憶體管理方案。請儘可能的找出與記憶體有關的錯誤,並回答其註釋中的附加題。

 

  //  為什麼B的delete還有第二個引數?


//  為什麼D的delete卻沒有第二個引數?


//


  class B {


  public:


  virtual ~B();


  void operator delete  ( void*, size_t ) throw();


  void operator delete[]( void*, size_t ) throw();


  void f( void*, size_t ) throw();


  };


 


  class D : public B {


  public:


  void operator delete  ( void* ) throw();


  void operator delete[]( void* ) throw();


  };


 


  void f()


  {


  //  下面各個語句中,到底哪一個delete被了?為什麼?


  //  呼叫時的引數是什麼?


  //


  D* pd1 = new D;


  delete pd1;


 


  B* pb1 = new D;


  delete pb1;


 


  D* pd2 = new D[10];


  delete[] pd2;


 


  B* pb2 = new D[10];


  delete[] pb2;


 


 //  下面兩個賦值語句合法嗎?


  //


  B b;


  typedef void (B::*PMF)(void*, size_t);


  PMF p1 = &B::f;


  PMF p2 = &B::operator delete;


  }


 


 


  class X {


  public:


  void* operator new( size_t s, int )


   throw( bad_alloc ) {


  return ::operator new( s );


  }


  };


 


 


  class SharedMemory {


  public:


  static void* Allocate( size_t s ) {


  return OsSpecificSharedMemAllocation( s );


  }


  static void  Deallocate( void* p, int i ) {


  OsSpecificSharedMemDeallocation( p, i );


  }


  };


 


  class Y {


  public:


  void* operator new( size_t s,


  SharedMemory& m ) throw( bad_alloc ) {


  return m.Allocate( s );


  }


 


  void  operator delete( void* p,


  SharedMemory& m,


  int i ) throw() {


  m.Deallocate( p, i );


  }


  };


 


 


  void operator delete( void* p ) throw() {


 SharedMemory::Deallocate( p );


  }


 


  void operator delete( void* p,


  std::nothrow_t& ) throw() {


  SharedMemory::Deallocate( p );


  }


 

 

[解答]

 

  //  為什麼B的delete還有第二個引數?


//  為什麼D的delete卻沒有第二個引數?


//


  class B {


  public:


  virtual ~B();


  void operator delete  ( void*, size_t ) throw();


  void operator delete[]( void*, size_t ) throw();


  void f( void*, size_t ) throw();


  };


 


  class D : public B {


  public:


  void operator delete  ( void* ) throw();


  void operator delete[]( void* ) throw();


  };


 


附加題答案:這是出於個人喜好(preference)。兩種都是正常的回收(譯註:作者在這裡用了“deallocation(去配)”一詞,鄙人一律翻譯為“回收”),而不是placement deletes(譯註:關於placement delete和placement new,可以閱讀Stanley Lippman的《Ins The C++ Model(深度探索C++模型)》一書中的6.2節:Placement Operator New的語意)。

 

另外,這兩個類都提供了delete和delete[],卻沒有提供相對應的new和new[]。這是非常危險的。你可以試想,當更下層的派生類提供了它自己的new和new[]時會發生什麼!

 

 

  void f()


  {


  //  下面各個語句中,到底哪一個delete被呼叫了?為什麼?


  //  呼叫時的引數是什麼?


  //


  D* pd1 = new D;


  delete pd1;


 


這裡呼叫了D::operator delete(void*)。

 

 

  B* pb1 = new D;


  delete pb1;


 


這裡呼叫D::operator delete(void*)。因為B的解構函式(destructor)被宣告成virtual,所以D的解構函式(destructor)理所當然會被正常呼叫,但同時這也意味著,即使B::operator delete()不宣告成virtual,D::operator delete()也必將被呼叫。

 

 

  D* pd2 = new D[10];


  delete[] pd2;


 


這裡D::operator delete[](void*)被呼叫。

 

 

  B* pb2 = new D[10];


  delete[] pb2;


 


這裡的行為是不可預料的。C++語言要求,傳遞給delete的指標之靜態型別必須與其之動態型別一樣。關於這個問題的進一步談論,可以參看tt Meyers在《Effective C++》或者《More Effective C++》中有關“Never Treat Arrays Polymorphically”的部分(譯註:這裡指的應該是《More Effective C++》中的條款3:絕對不要以多型方式處理陣列)。

 

 

  //  下面兩個賦值語句合法嗎?


  //


  B b;


 typedef void (B::*PMF)(void*, size_t);


  PMF p1 = &B::f;


  PMF p2 = &B::operator delete;


  }


 


第一個賦值語句沒問題,但是第二個是不合法的,因為  “void operator delete(void*,size_t)throw()”並不是B的成員函式,即使它被寫在上面使其看上去很像是。這裡有一個小伎倆需要弄清楚,即new和delete總是靜態的,即使它們不被顯式的宣告為static。總是把它們宣告為static是個很好的習慣,這可以讓所有閱讀你程式碼的程式設計師們明白無誤的認識到這一點。

 

 

  class X {


  public:


  void* operator new( size_t s, int )


  throw( bad_alloc ) {


  return ::operator new( s );


  }


  };


 


這會產生記憶體洩漏,因為沒有相應的placement delete存在。下面的程式碼也是一樣:

 

 

  class SharedMemory {


  public:


  static void* Allocate( size_t s ) {


  return OsSpecificSharedMemAllocation( s );


  }


  static void  Deallocate( void* p, int i ) {


   OsSpecificSharedMemDeallocation( p, i );


  }


  };


 


  class Y {


  public:


  void* operator new( size_t s,


  SharedMemory& m ) throw( bad_alloc ) {


  return m.Allocate( s );


  }


 


這裡也產生記憶體洩漏,因為沒有對應的delete。如果在用這個函式分配的記憶體裡放置物件的構造過程中丟擲了異常,記憶體就不會被正常釋放。例如:

 

  SharedMemory shared;


  ...


  new (shared) T; // if T::T() throws, memory is leaked


 

在這裡,記憶體還無法被的刪除,因為類中沒有提供正常的operator delete。這意味著,基類或者派生類的operator delete(或者是那個全域性的delete)將會試圖處理這個回收操作(這幾乎肯定會失敗,除非你替換掉周圍環境中所有類似的delete——這實在是一件繁瑣和可怕的事情)。

 

 

  void  operator delete( void* p,


  SharedMemory& m,


  int i ) throw() {


  m.Deallocate( p, i );


  }


  };


 


這裡的這個delete毫無用處,因為它從不會被呼叫。

 

 

  void operator delete( void* p ) throw() {


  SharedMemory::Deallocate( p );


  }


 


這是一個嚴重的錯誤,因為它將要刪除那些被預設的::operator new分配出來的記憶體,而不是被SharedMemory::Allocate()分配的記憶體。你頂多只能盼來一個迅速的core dump。真是見鬼!

 

 

  void operator delete( void* p,


   std::nothrow_t& ) throw() {


  SharedMemory::Deallocate( p );


  }


 

這裡也是一樣,只是更為微妙。這裡的delete只會在“new(nothrow)T”失敗的時候才會被呼叫,因為T的建構函式(constructor)會帶著一個異常來終止,並企圖回收那些不是被SharedMemory::Allocate()分配的記憶體。這真是又邪惡又陰險!

 

 

好,如果你回答出了以上所有的問題,那你就肯定是走在成為記憶體管理機制專家的光明大道上了。


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

相關文章