最近小組讀書活動讓我對 placement new 和 placement delete 有了更加深入的理解.
關于new表達式
C++ 提供了new
關鍵字和delete
關鍵字, 分別用于申請和釋放內存空間, 其中new
表達式的語法如下:
new new-type-id ( optional-initializer-expression-list )
書上說, new
表達式做兩件事情
- 在堆(heap)空間上申請一塊空間, 大小等于
sizeof(new-type-id)
- 在申請的空間上構建對象, 即調用對象的構造函數
另外, 我了解到, 如果用戶希望在自定義內存空間上構造對象, 可以調用另一個new
表達式, 語法如下:
new ( expression-list ) new-type-id ( optional-initializer-expression-list )
具體使用起來, 就像這樣:
void *buffer = malloc(sizeof(ClassA));
ClassA *ptr = new(buffer)ClassA();
第二種new
表達式就像第一種的特例, 只需要完成步驟2, 而把步驟1留給用戶自理.
關于delete表達式##
C++ 提供的delete表達式的語法:
delete type_pointer;
書上說, delete表示完成兩件事情:
- 調用對象的析構函數,
- 釋放對象所在的內存空間, 返回給系統
特別注意的是, 讓type_pointer
等于NULL, delete表達式也能正確功能, 但是對同一個指針, 重復調用delete
將帶來未定義錯誤
標準 operator new 和 operator delete##
C++ 標準的 new expression 內部調用的是標準的operator new()
, 它的定義如下:
void * operator new (std::size_t) throw(std::bad_alloc);
operator new()
是一個操作符或者函數, 它完成標準的allocation, 即內存分配.
類似的, C++標準的delete expression 內部調用的是標準的operator delete()
, 它的定義如下:
void operator delete (void *) throw();
operator delete()
也是一個操作符或者函數, 它完成標準的deallocation, 即內存釋放
placement new 和 placement delete##
C++標準的 new 表達式能完成大部分的需求, 但不是全部, 比如: 如何在已有的內存空間上創建對象, 標準 new 表達式做不了, C++也不支持直接在raw內存上調用對象的構造函數, 于是placement new就產生了, 取名placement
, 也說明了它的歷史來源, 即在原地
構建一個新對象.
當然原地創建對象只是一部分, placement new有更廣大的外延, 而且placement new expression和placement operator new()
, 通常都被籠統的成為placement new, 混淆了概念.
什么是placement operator new()
, 它首先是一個函數, 而且是標準operator new()
的一個重載函數. wiki中這么定義它:
The "placement" versions of the new and delete operators and functions are known as placement new and placement delete.
什么是placement new expression, 它屬于C++語法, 類似于標準的new expression, 不同的是,內部調用的是相應的placement operator new()
, wiki上這么定義它:
Any new expression that uses the placement syntax is a placement new expression
就像標準 new expression 調用的是標準 operator new()
一樣, placement new expression 調用的是placement版本的operator new()
, 看起來很簡單直白, 混在一起似乎也問題不大, 但是看看placement delete, 它就表示 placement operator delete()
函數, 因為根本不存在placement delete expression, 當我們調用
delete pObject;
我們調用的是標準的operator delete()
. 為什么沒有placement delete expression, C++的締造者給出的解釋是:
The reason that there is no built-in "placement delete" to match placement new is that there is no general way of assuring that it would be used correctly.
既然沒有placement delete expression, 那為啥還需要placement operator delete()
, 一個重要的原因是, C++需要placement operator delete()
和placement operator new()
成雙成對. 假設一種情況:
當你調用placement new expression構建對象, 結果在構造函數中拋出異常, 這個時候怎么辦, C++ 只能調用相應的placement operator delete()
, 釋放由placement operator new()
獲取的內存資源, 否則就會有內存泄露. 通過下面的例子可以感受一下:
#include <cstdlib>
#include <iostream>
struct A {} ;
struct E {} ;
class T {
public:
T() { throw E() ; }
} ;
void * operator new ( std::size_t, const A & )
{std::cout << "Placement new called." << std::endl;}
void operator delete ( void *, const A & )
{std::cout << "Placement delete called." << std::endl;}
int main ()
{
A a ;
try {
T * p = new (a) T ;
} catch (E exp) {std::cout << "Exception caught." << std::endl;}
return 0 ;
}
placement operator new() 和 placement operator delete()
上文只是界定了expression和operator的區別, 那什么樣的函數才叫placement operator new()
和placement operator delete()
, 他們和標準operator new()
以及operator delete()
的區別就添加了自定義參數. 但是必須遵守以下規則.
對于placement operator new()
, 它的第一個函數參數必須是std::size_t
, 表示申請的內存的大小; 對于placement operator delete()
, 它的第一個函數參數必須是void *
, 表示要釋放的對象指針. 比如:
void * operator new (std::size_t) throw(std::bad_alloc); // 標準版本
void * operator new (std::size_t, const std::nothrow_t &) throw(); // placement 版本
void operator delete (void *) throw(); // 標準版本
void operator delete (void *, const std::nothrow_t &) throw(); // placement 版本
請注意nothrow版本的new, 也是C++標準, 它就是通過placement new和placement delete實現的.相應的nothrow 版本的new expression是這樣:
T *t = new(std::nothrow) T;
在用戶自定義空間上構建對象, 是placement new的本意, 它也被做到C++標準中, 作為default placement:
void * operator new (std::size_t, void * p) throw() { return p ; }
void operator delete (void *, void *) throw() { }
相應的 placement new expression 使用起來就是這樣:
void *buffer = malloc(sizeof(ClassA));
ClassA *ptr = new(buffer)ClassA();
mempool用到的placement new
實際應用中, 內存池用到最多, 值得一講, 假設一個內存池的定義如下:
class Arena {
public:
void* allocate(size_t);
void deallocate(void*);
// ...
};
通過定義placement new和placement delete作用于Arena
void* operator new(size_t sz, Arena& a)
{
return a.allocate(sz);
}
void operator delete(void *ptr, Arena& a)
{
return a.deallocate(ptr);
}
創建對象:
Arena a();
X *p = new(a)X;
現在的問題是怎么delete
, 因為我們不能調用
delete p;
這樣只能啟用的標準的operator delete()
, 而不是針對Arena
的placement版本, 為此, 我們只能這么做:
p->~X();
operator delete(p, a);
或者把兩個操作封裝成一個template:
template<class T> void destroy(T* p, Arena& a)
{
if (p) {
p->~T(); // explicit destructor call
a.deallocate(p);
}
}
然后這么調用
destroy(p,a);