C與C++中的異常處理9 (轉)
丟擲的異常: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++標準執行庫的
l
我想,這個結束了我對plcement new和delete及它們在處理建構函式丟擲的異常時扮演的角色的解釋。下次,我將介紹給你一個不同的技巧來容忍建構函式丟擲的異常。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992214/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- C與C++中的異常處理 (轉)C++
- C與C++中的異常處理11 (轉)C++
- C與C++中的異常處理13 (轉)C++
- C與C++中的異常處理12 (轉)C++
- C與C++中的異常處理14 (轉)C++
- C與C++中的異常處理15 (轉)C++
- C與C++中的異常處理16 (轉)C++
- C與C++中的異常處理17 (轉)C++
- C與C++中的異常處理3 (轉)C++
- C與C++中的異常處理4 (轉)C++
- C與C++中的異常處理5 (轉)C++
- C與C++中的異常處理7 (轉)C++
- C與C++中的異常處理6 (轉)C++
- C與C++中的異常處理8 (轉)C++
- C與C++中的異常處理10 (轉)C++
- c++異常處理 (轉)C++
- C與C++中的異常處理2(part2) (轉)C++
- C與C++中的異常處理2(part1) (轉)C++
- C++ 異常處理C++
- C++異常處理C++
- C++異常處理與臨時副本C++
- c++異常處理格式C++
- c++ 異常處理(2)C++
- c++ 異常處理(1)C++
- windows核心程式設計---未處理異常,向量化異常處理與C++異常Windows程式設計C++
- 【C++】 C++異常捕捉和處理C++
- C++異常處理機制C++
- 深入理解C++中的異常處理機制C++
- C++錯誤和異常處理C++
- C++整理19_異常處理C++
- C++ 異常處理機制詳解:輕鬆掌握異常處理技巧C++
- 【C++】 63_C語言異常處理C++C語言
- C++ 異常處理機制的實現C++
- 強制型別轉換時的異常處理_java與c++比較型別JavaC++
- Linux 下 C++ 異常處理技巧LinuxC++
- C++和結構化異常處理C++
- C/C++學習筆記八(斷言與異常處理)C++筆記
- Kotlin DSL C++專案引入OpenCV異常處理(轉)KotlinC++OpenCV