c++ new用法 - “放置新的”有什麼用處?




11 Answers

Placement new允許您在已經分配的內存上構建一個對象。

您可能希望這樣做優化(不要一直重新分配會更快),但需要多次重新構建對象。 如果您需要繼續重新分配,即使您不想使用它,分配的分配量也可能比您需要的分配效率更高。

Devex給出了一個很好的例子

標準C ++還支持放置新操作符,它在預分配的緩衝區上構建一個對象。 這在構建內存池,垃圾回收器時或僅僅在性能和異常安全性至關重要時(這是沒有分配失敗的危險,因為內存已被分配,並且在預分配的緩衝區上構建對象需要更少的時間) :

char *buf  = new char[sizeof(string)]; // pre-allocated buffer
string *p = new (buf) string("hi");    // placement new
string *q = new string("hi");          // ordinary heap allocation

您可能還想確保在關鍵代碼的某個部分沒有分配失敗(例如,您可能在使用起搏器工作)。 在這種情況下,您會希望使用新的展示位置。

取消分配新的分配

您不應該釋放每個使用內存緩衝區的對象。 相反,您應該只刪除[]原始緩衝區。 您將不得不手動直接調用類的析構函數。 有關此方面的好建議,請參閱Stroustrup的常見問題解答: 是否存在“展示位置刪除”

dynamic memory

有沒有人曾經使用過C ++的“放置新”? 如果是這樣,為了什麼? 它看起來像它只會在內存映射硬件上有用。




如果你想分配分配和初始化,這很有用。 STL使用placement new來創建容器元素。




我用它通過alloca()來構建堆棧中分配的對象。

無恥的插件:我在here博客。




我用它來創建一個Variant類(即可以表示一個可以是多種不同類型的單個值的對象)。

如果Variant類支持的所有值類型都是POD類型(例如int,float,double,bool),那麼標記的C樣式聯合就足夠了,但是如果您希望某些值類型是C ++對象例如std :: string),C union功能將不會執行,因為非POD數據類型可能不會被聲明為聯合的一部分。

因此,我將分配一個足夠大的字節數組(例如sizeof(the_largest_data_type_I_support)),並使用placement new來在該變量設置為保存該類型的值時初始化該區域中的相應C ++對象。 (當然,當從不同的非POD數據類型切換時預先放置刪除)




序列化時(比如用boost :: serialization),Placement new也很有用。 在10年的c ++中,這僅僅是我需要第二種情況的放置(第三如果你包括訪談:))。




我認為這個問題沒有被任何答案突出顯示,但新放置的另一個很好的例子和用法是減少內存碎片(通過使用內存池)。 這在嵌入式和高可用性系統中特別有用。 在這最後一種情況下,這是特別重要的,因為對於必須運行24/365天的系統來說,沒有碎片非常重要。 這個問題與內存洩漏無關。

即使使用了非​​常好的malloc實現(或類似的內存管理功能),也很難長時間處理碎片。 在某些時候,如果你不巧妙地管理內存預留/釋放調用,最終可能會產生許多難以重用的小間隙 (分配給新的預留)。 因此,在這種情況下使用的解決方案之一是使用內存池來為應用程序對象分配內存。 每次您需要內存來存儲某個對像後,您只需使用新的位置在已保留的內存上創建一個新對象。

這樣,一旦你的應用程序啟動,你已經保留了所有需要的內存。 所有新的內存預留/釋放都會進入分配的池(您可能有多個池,每個不同的對像類都有一個池)。 在這種情況下不會發生內存碎片,因為不會出現間隙,並且您的系統可以運行很長時間(幾年)而不會出現碎片。

我特別在VxWorks實時操作系統的實踐中看到了這一點,因為它的默認內存分配系統遭受了很多碎片。 所以通過標準的new / malloc方法分配內存在項目中基本上是被禁止的。 所有的內存預留應該去專用的內存池。




我用它來存儲帶有內存映射文件的對象。
具體的例子是一個圖像數據庫,處理大量的大圖像(超過內存容量)。




我已經看到它被用作“動態類型”指針輕微性能攻擊 (在“Under the Hood”中):

但是這裡有一個棘手的技巧,我用它來獲得快速的小類型性能:如果被保存的值可以放入void *中,我實際上並不打擾分配一個新對象,我使用放置new來強制它進入指針本身。







腳本引擎可以在本地接口中使用它來從腳本分配本地對象。 有關示例,請參閱Angelscript(www.angelcode.com/angelscript)。




實際上,實現任何種類的數據結構都需要實現,這些數據結構分配的內存數量要比插入的元素數量(即,除了一次分配一個節點的鏈接結構以外的任何其他內存)所需的最小數量要多。

使用unordered_mapvectordeque等容器。 這些所有內存分配的內存都比您迄今為止插入的元素所需的內存要少,以避免每次插入都需要堆分配。 讓我們使用vector作為最簡單的例子。

當你這樣做時:

vector<Foo> vec;

// Allocate memory for a thousand Foos:
vec.reserve(1000);

......實際上並沒有構建一千個Foos。 它只是為他們分配/保留內存。 如果vector在這裡沒有使用放置位置,那麼它將默認構建Foos所有位置,甚至不得不調用它們的析構函數,即使對於您從未插入到位的元素也是如此。

分配!=建築,釋放!=毀滅

總而言之,為了實現像上面這樣的許多數據結構,你不能把分配內存和構造元素看作是一件不可分割的事情,你也不能把釋放內存和摧毀元素當作一件不可分割的事情。

這些想法之間必須有一個分離,以避免不必要地左右調用構造函數和析構函數,這就是為什麼標準庫分離std::allocator的想法(當它分配/釋放內存時不會構造或銷毀元素*)遠離使用它的容器,它使用放置新手動構建元素,並使用顯式調用析構函數手動銷毀元素。

  • 我討厭std::allocator的設計,但這是一個不同的主題,我會避免咆哮。 :-D

因此,無論如何,由於我已經編寫了許多通用的符合標準的C ++容器,因此無法根據現有的容器進行構建,因此我傾向於使用它。 其中包括我幾十年前為避免常見情況下的堆分配而構建的小型矢量實現,以及高效的內存樹(一次不分配一個節點)。 在這兩種情況下,我都無法真正使用現有容器來實現它們,因此我必須使用placement new來避免不必要地左右調用構造函數和析構函數。

當然,如果您曾經使用自定義分配器來分配對象(如自由列表),那麼您通常也希望使用placement new ,就像這樣(基本示例不打擾異常安全或RAII):

Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);



Related

c++ memory-management new-operator