C與C++中的異常處理9 (轉)

worldblog發表於2007-12-12
C與C++中的異常處理9 (轉)[@more@]

丟擲的異常:namespace prefix = o ns = "urn:schemas--com::office" />

  當被了來清理部分構造時,operator delete的第一個void *引數帶的是的地址(剛剛由對應的operator new返回的)。operator delete的所有額外placement引數都和傳給operator new的相應引數的值相匹配。

  在程式碼裡,語句

p = new(n1, n2, n3) T(c1, c2, c3);

的效果是

p = operator new(sizeof(T), n1, n2, n3);

T(p, c1, c2, c3);

 

  如果T(p, c1, c2, c3)建構函式丟擲了一個異常,暗中呼叫

operator delete(p, n1, n2, n3);

  原則:當釋放一個部分構造的物件時,operator delete從原始的new語句知道上下文。

 

1.1  Placement operator delete的引數

  要證明這點,增強我們的例子來跟蹤相應的引數值:

// Example 11

 

#include

#include

 

class B

  {

public:

  B(int const ID) : ID_(ID)

  {

  std::cout << ID_ << " B::B enter" << std::endl;

  if (ID_ > 2)

  {

  std::cout << std::endl;

  std::cout << "  THROW" << std::endl;

  std::cout << std::endl;

  throw 0;

  }

  std::cout << ID_ << " B::B exit" << std::endl;

  }

  ~B()

  {

  std::cout << ID_ << " B::~B" << std::endl;

  }

  //

  // non-placement

  //

  void *operator new(size_t const n)

  {

  void *const p = ::operator new(n);

  std::cout << "  B::operator new(" << n <<

  ") => " << p << std::endl;

  return p;

  }

  void operator delete(void *const p)

  {

  std::cout << "  B::operator delete(" << p <<

  ")" << std::endl;

  ::operator delete(p);

  }

  //

  // placement

  //

  void *operator new(size_t const n, int const i)

  {

  void *const p = ::operator new(n);

  std::cout << "  B::operator new(" << n <<

  ", " << i << ") => " << p << std::endl;

  return p;

  }

  void operator delete(void *const p, int const i)

  {

  std::cout << "  B::operator delete(" << p <<

  ", " << i << ")" << std::endl;

  ::operator delete(p);

  }

private:

  int const ID_;

  };

 

class A

  {

public:

  A() : b1(new(11) B(1)), b2(new(22) B(2)), b3(new(33) B(3))

  {

  std::cout << "  A::A" << std::endl;

  }

  ~A()

  {

  std::cout << "  A::~A" << std::endl;

  }

private:

  std::auto_ptr const b1;

  std::auto_ptr const b2;

  std::auto_ptr const b3;

  };

 

int main()

  {

  try

  {

  A a;

  }

  catch(...)

  {

   std::cout << std::endl;

  std::cout << "  CATCH" << std::endl;

  std::cout << std::endl;

  }

  return 0;

  }

  用Visual C++ 6編譯並執行。在我的機器上的輸出是:

  B::operator new(4, 11) => 007E0490

1 B::B enter

1 B::B exit

  B::operator new(4, 22) => 007E0030

2 B::B enter

2 B::B exit

  B::operator new(4, 33) => 007E0220

3 B::B enter

 

  THROW

 

  B::operator delete(007E0220, 33)

2 B::~B

  B::operator delete(007E0030)

1 B::~B

  B::operator delete(007E0490)

 

  CATCH

 

  注意這些數字:

l  4是每個被分配的B物件的大小的位元組數。這個值在不同的C++實現下差異很大。

l  如007E0490這樣的值是operator new返回的物件的地址,作為this指標傳給T的成員函式的,並作為void *型指標傳給operator delete。你看到的值幾乎肯定和我的不一樣。

l  11,22和33是最初傳給operator new的額外placement引數,並在部分構造時傳給相應的placement operator delete。

 

1.2  手工呼叫operator delete

  所有這些operator new和operator delete的自動匹配是很方便的,但它只在部分構造時發生。對通常的完全構造,operator delete不是被自動呼叫的,而是透過明確的delete語句間接呼叫的:

p = new(1) B(2); // calls operator new(size_t, int)

// ...

delete p;  // calls operator delete(void *)

  這樣的順序其結果是呼叫placement operator new和非placement operator delete,即使你有對應的(placement)operator delete可用。

  雖然你很期望,但你不能用這個方法強迫呼叫placement operator delete:

delete(1) p; // error

而必須手工寫下delete語句將要做的事:

p->~B();  // call *p's destructor

B::operator delete(p, 1); // call placement

  //  operator delete(void *, int)

  要和自動呼叫operator delete時的行為保持完全一致,你必須儲存透過new語句傳給operator new的引數,並將它們手工傳給operator delete。

p = new(n1, n2, n3) B;

// ...

p->~B();

B::operator delete(p, n1, n2, n3);

 

1.3  其它非placement delete

  貫穿整個這個專題,我說了operator new和operator delete分類如下:

函式對

l  void *operator new(size_t)

l  void operator delete(void *)

是非placement分配和釋放函式。

所有如下形式的函式對

l  void *operator new(size_t, P1, ..., Pn)

l  void operator delete(void *, P1, ..., Pn)

是placement分配和釋放函式。

  我這樣說是因為簡潔,但我現在必須承認撒了個小謊:

void operator delete(void *, size_t)

也可以是一個非placement釋放函式而匹配於

void *operator new(size_t)

雖然它有一個額外引數。如你所猜想,operator delete的size_t引數帶的是傳給operator new的size_t的值。和其它額外引數不同,它是提供完全構造的物件用的。

  在我們的例子中,將這個size_t引數加到非placement operator delete上:

// Example 12

 

// ... preamble unchanged

 

class B

  {

  void operator delete(void * const p, size_t const n)

  {

  std::cout << "  B::operator delete(" << p <<

  ", " << n << ")" << std::endl;

  ::operator delete(p);

  }

  // ... rest of class B unchanged

  };

 

// ... class A and main unchanged

The results:

 

  B::operator new(4, 11) => 007E0490

1 B::B enter

1 B::B exit

  B::operator new(4, 22) => 007E0030

2 B::B enter

2 B::B exit

  B::operator new(4, 33) => 007E0220

3 B::B enter

 

  THROW

 

  B::operator delete(007E0220, 33)

2 B::~B

  B::operator delete(007E0030, 4)

1 B::~B

  B::operator delete(007E0490, 4)

 

  CATCH

  注意,為完全構造的物件,將額外的引數4提供給了operator delete。

 

1.4  顯而易見的矛盾

  你可能奇怪:C++標準允許非placement operator delete自動知道一個物件的大小,卻否定了placement operator delete可具有相同的能力。要想使它們保持一致,一個placement分配函式

void *operator new(size_t, P1, P2, P3)

應該匹配於這樣一個placement釋放函式

void operator delete(void *, size_t, P1, P2, P3)

  但事實不是這樣,這兩個函式不匹配。為什麼語言被這樣設計?我猜有兩個原因:和清晰。

  大部分情況下,operator delete不需要知道一個物件的大小;強迫函式任何時候都接受大小引數是低效的。並且,如果標準允許size_t引數可選,這樣的含糊將造成:

void operator delete(void *, size_t, int)

在不同的環境下有不同的意義,決定它將匹配哪個:

void *operator new(size_t, int)

還是

void *operator new(size_t, size_t, int)

  如果因下面的語句拋了個異常而被呼叫:

p = new(1) T; // calls operator new(size_t, int)

operator delete的size_t引數將是sizeof(T);但如果是被呼叫時是

p = new(1, 2) T; // calls operator new(size_t, size_t, int)

operator delete的size_t引數將是new語句的第一個引數值(這裡是1)。於是,operator delete將不知道怎麼解釋它的size_t值。

  我估計,你可能想知道是否非placement的函式

void operator delete(void *, size_t)

同時作為一個placement函式匹配於

void *operator new(size_t, size_t)

  如果它被允許,operator delete將遇到前面講的同樣問題。而不被允許的話, C++標準將需要其規則的一個例外。

  我沒發現規則的這樣一個例外。我試過幾個編譯器,— including EDG’s front end, my expert witness on such matters — 並認為:

void operator delete(void *, size_t)

實際上能同時作為一個placement釋放函式和一個非placement釋放函式。這是個重要的提醒。

  如果你懷疑我,就將例12的placement operator delete移掉。

// Example 13

 

// ... preamble unchanged

 

class B

  {

//  void operator delete(void *const p, int const i)

//  {

//  std::cout << "  B::operator delete(" << p <<

//  ", " << i << ")" << std::endl;

//   ::operator delete(p);>

//  }

  // ... rest of class B unchanged

  };

 

// ... class A and main unchanged

 

  現在,類裡有一個operator delete匹配於兩個operator new。其輸出結果和例12仍然相同。(WQ注:結論是正確的,但不同的編譯器下對例12到例14的反應相差很大,很是有趣!)

 

1.5  結束

  兩個最終要點:

l  貫穿我整個對::operator new和B::operator delete的討論,我總是將函式申明為非static。通常這樣的申明意味著有this指標存在,但這些函式的行為象它們沒有this指標。實際上,在這些函式來試圖引用this,你將發現程式碼不能編譯。不象其它成員函式,operator new和operator delete始終是static的,即使你沒有用static關鍵字。

l  無論我在哪兒提到operator new和operator delete,你都可以用operator new[] 和operator delete[]代替。相同的,相同的規則,和相同的觀察結果。(雖然Visual C++標準執行庫的中缺少operator new[]和operator delete[],編譯器仍然允許你定義自己的陣列版本。)

l

  我想,這個結束了我對plcement new和delete及它們在處理建構函式丟擲的異常時扮演的角色的解釋。下次,我將介紹給你一個不同的技巧來容忍建構函式丟擲的異常。


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

相關文章