理解C++ placement語法

最近小組讀書活動讓我對 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);

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容