閱讀886 返回首頁    go 阿裏雲 go 技術社區[雲棲]


C++編程規範之45:總是一起提供new和delete

摘要:

它們是一攬子交易:每個類專門的重載void*operator new(parms)都必須與對應的重載void operator delete(void*, params)相隨相伴,其中parms是額外參數類型的一個列表(第一個總是std:size_t)。數組形式的new[]和delete[]也同樣如此。

    很少需要提供自定義的new或者delete,但是如果需要其中一個,那麼通常兩個都需要。如果定義了類專門的T::operator new進行某種特殊的分配操作,很可能還需要定義一個類專門的T::operator delete進行相應的釋放操作。

    這些闡述可能有些過於基礎了,但是之所以要加入本條款,有一個更加微妙的原因:編譯器可能需要T::operator delete的重載,即使實際上從來也不會調用它。這才是為什麼要成對提供operator new和operator delete(以及operator new[]和operator delete[])的原因。

    假設定義了一個帶有自定義分配操作的類:

class T

{

         //……

         Static void* operator new(std::size_t)

         Static void* operator new(std::size_t,CustomAllocator&)

 

         Static void operator delete(void*,std::size_t)

};

這樣就為分配和釋放建立了一個簡單的協議。

1.調用者能夠用默認的分配器(使用new T)或者自定義的分配器,(使用new(alloc)T,其中alloc是一個customAllactor類型的對象)來分配類型T的對象。

2.唯一調用者可能調用的operatordelete是默認的operator delete(size_t),因此當然應該實現,從而能夠正確地釋放已分配的內存。

到目前為止,一切正常。

但是編譯器冉冉需要秘密地調用另一個delete重載,即T::operatordelete(size_t, CustomAllocator&)。這是因為語句

T* p = new(alloc)T;

實際上將擴展為類似下麵的代碼:

//編譯器為T* p =new(alloc)T;生成的代碼

//

Void*_compilerTemp = T::operator new (sizeof(T),alloc);

T* p;

Try

{

         P = new(_compilerTemp)T;

}

Catch(…)

{

         T::operator delete(_compilerTemp,sizeof(T), alloc);

         throw

}

    因此,如果分配成功,但是構造函數失敗了,那麼編譯器將順理成章地自動插入代碼,為重載的T::operator new調用對應的T::operator delete。對應的簽名是void operator delete(void*,whatever-parameters-new-takes).

    下麵是煞風景的部分了。C++ Standard規定,當且僅當operatordelete的重載實際退出時,以上代碼才能生成。否則,在構造函數失敗的情況下,代碼不會調用任何operator delete。也就是說,如果構造函數失敗,內存將泄漏。

    正因為如此,重載void*operator new(parms)必須伴有與其對應的重載void operator delete(void* ,params)——因為編譯器自己要調用它們。

最後更新:2017-04-03 12:55:09

  上一篇:go openstack 命令行管理十二 - 內部網絡[instance專用]管理 (備忘)
  下一篇:go C# Show()與ShowDialog()的區別