[英]How does C++ free the memory when a constructor throws an exception and a custom new is used
我看到以下結構:
如果X
構造函數拋出, new X
將釋放內存。
operator new()
可以重載。
運算符 new 重載的規范定義是void *operator new(size_t c, heap h)
和相應的operator delete
。
最常見的 operator new 重載是placement new,即void *operator new(void *p) { return p; }
void *operator new(void *p) { return p; }
您幾乎總是不能在給定new
的指針上調用delete
。
這導致了一個問題:當X
構造函數拋出並使用重載的new
時,如何清理內存?
從根本上說,如果沒有對應於new
操作符的delete
操作符,那么什么都不做。 在放置新的情況下也不做任何事情,因為相應的放置刪除操作符是空操作。 異常沒有轉移:它繼續它的進程,所以 new 的調用者有機會(和責任)釋放分配的內存。
之所以稱為 Placement new,是因為它用於將對象放置在以其他方式獲取的內存中; 由於內存不是由 new 操作符獲取的,因此它不太可能被刪除操作符釋放。 實際上,這個問題沒有實際意義,因為(至少從 C++03 開始)不允許替換放置 new 運算符(它具有原型operator new(size_t, void*)
或 delete ( operator delete(void*, void*)
). 提供的placement new 操作符返回它的第二個參數,並且提供的placement delete 操作符是一個空操作。
其他new
和delete
運算符可能會被替換,無論是全局的還是特定的類。 如果調用自定義的new
運算符,並且構造函數拋出異常,並且存在相應的delete
運算符,則將在傳播異常之前調用該 delete 運算符進行清理。 但是,如果沒有相應的delete
運算符,則不會出錯。
先舉個例子:
#include <cstddef>
#include <iostream>
struct S
{
S(int i) { if(i > 42) throw "up"; }
static void* operator new(std::size_t s, int i, double d, char c)
{
std::cout << "allocated with arguments: "
<<i<<", "<<d<<", "<<c<<std::endl;
return new char[s];
}
static void operator delete(void* p, int i, double d, char c)
{
std::cout << "deallocated with arguments: "
<<i<<", "<<d<<", "<<c<<std::endl;
delete[] (char*)p;
}
static void operator delete(void* p)
{
std::cout << "deallocated w/o arguments"<<std::endl;
delete[] (char*)p;
}
};
int main()
{
auto p0 = new(1, 2.0, '3') S(42);
S* p1 = nullptr;
try
{
p1 = new(4, 5.0, '6') S(43);
}catch(const char* msg)
{
std::cout << "exception: "<<msg<<std::endl;
}
delete p1;
delete p0;
}
輸出:
allocated with arguments: 1, 2, 3 allocated with arguments: 4, 5, 6 deallocated with arguments: 4, 5, 6 exception: up deallocated w/o arguments
運算符 new 重載的規范定義是
void *operator new(std::size_t, heap h)
我不明白這是如何規范的,因為它是不允許的:
好的,現在它是new
的有效放置形式:)
[basic.stc.dynamic.allocation]/1
分配函數應為類成員函數或全局函數; 如果分配函數在全局范圍以外的命名空間范圍中聲明或在全局范圍內聲明為靜態,則程序格式錯誤。 返回類型應為
void*
。 第一個參數的類型應為std::size_t
。 第一個參數不應有關聯的默認參數。 第一個參數的值應解釋為請求的分配大小。
[強調我的]
您可以重載要為new
的放置形式調用的分配函數,請參閱 [expr.new] (在 [basic.stc.dynamic.allocation] 中未明確允許非模板函數,但也不禁止)。 new(placement)
給出的放置在這里被推廣到一個expression-list 。 特定新表達式的表達式列表中的每個表達式都作為附加參數傳遞給分配函數。 如果調用釋放函數(例如,因為被調用的構造函數拋出異常),則將相同的參數加上前導void*
(分配函數的返回值)傳遞給釋放函數。
[expr.new]/18 指出:
如果上面描述的對象初始化的任何部分因拋出異常而終止,已經為對象獲得了存儲空間,並且可以找到合適的釋放函數,則調用釋放函數釋放正在構造對象的內存,之后異常繼續在new-expression的上下文中傳播。 如果找不到明確匹配的釋放函數,則傳播異常不會導致對象的內存被釋放。 [注意:這適用於被調用的分配函數不分配內存的情況; 否則很可能導致內存泄漏。 —尾注]
和 /21
如果new 表達式調用釋放函數,它將從分配函數調用返回的值作為
void*
類型的第一個參數傳遞。 如果調用放置解除分配函數,則傳遞與傳遞給放置分配函數相同的附加參數,即與使用 new-placement 語法指定的參數相同的參數。
和 /20
如果放置釋放函數的聲明具有相同數量的參數,並且在參數轉換后,除第一個參數類型之外的所有參數類型都相同,則放置釋放函數的聲明與放置分配函數的聲明匹配。 任何非布局解除分配函數都與非布局分配函數相匹配。 如果查找找到單個匹配的解除分配函數,則將調用該函數; 否則,不會調用任何釋放函數。 如果查找找到了一個普通釋放函數的雙參數形式,並且該函數被視為放置釋放函數,將被選為分配函數的匹配項,則程序格式錯誤。 [示例:
struct S { // Placement allocation function: static void* operator new(std::size_t, std::size_t); // Usual (non-placement) deallocation function: static void operator delete(void*, std::size_t); }; S* p = new (0) S; // ill-formed: non-placement deallocation function matches // placement allocation function
—結束示例]
回到[basic.stc.dynamic.deallocation]:
1 釋放函數應為類成員函數或全局函數; 如果在全局范圍以外的命名空間范圍內聲明釋放函數或在全局范圍內聲明為靜態,則程序格式錯誤。
2 每個解除分配函數應返回
void
且其第一個參數應為void*
。 一個釋放函數可以有多個參數。
當構造函數拋出異常時,將調用匹配的刪除。 不會為拋出的類調用析構函數,但成功調用其構造函數的類的任何組件都將調用其析構函數。
'placement new' 不是 new 的重載版本,而是 operator new 的一種變體,也是一種不能重載的變體。
See 可以在此處查看新運算符的列表以及有關重載它們如何工作的描述。
如果構造函數在使用placement new 時拋出異常,編譯器就會知道使用了什么new 運算符並調用placement delete。
當作為new 表達式的一部分構造的對象的構造失敗時,將調用相應的解除分配函數(如果有的話)。 例如
new X;
將使用以下一對分配/解除分配函數。
void * operator new(std::size_t);
void operator delete(void *);
同樣,對於表單的新展示位置
new(&a) X;
將使用operator new
和operator delete
函數的放置版本。
void * operator new(std::size_t, void *);
void operator delete(void *, void *);
請注意,最后一個函數故意不執行任何操作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.