1、背景
在 C++ 中,如果你需要為類自定義 new 和 delete,必須遵循一些約定和規則,以確保內存管理的一致性、可維護性和安全性。當我們使用 new 和 delete 操作時,C++ 編譯器會:
- 調用全局或類特定的 operator new 來分配內存。
- 調用構造函數(new)或析構函數(delete)。
- 如果需要,調用全局或類特定的 operator delete 來釋放內存。
通常,類的內存管理行為依賴于全局版本的 operator new 和 operator delete,但在某些場景下,你可能需要為類定義自定義的版本。
2、自定義new和delete的基本規則
2.1、成對出現
如果為類定義了自定義的 operator new,則必須同時定義對應的 operator delete。
#include <iostream>
#include <cstdlib>class Widget {
public:static void* operator new(size_t size) {std::cout << "Custom operator new: Allocating " << size << " bytes" << std::endl;return std::malloc(size);}static void operator delete(void* ptr) noexcept {std::cout << "Custom operator delete: Freeing memory" << std::endl;std::free(ptr);}
};int main() {/*下面這句會執行兩步:1、調用 operator new(size_t size) 為對象分配內存,在執行這一步時,會將sizeof(Widget)作為參數2、調用對象的構造函數在分配的內存上初始化對象。*/Widget* w = new Widget;delete w;/*Custom operator new: Allocating 1 bytesCustom operator delete: Freeing memory*/return 0;
}
2.2、匹配的內存分配和釋放
- 任何通過 operator new 分配的內存,必須使用對應的 operator delete 釋放。
- 避免跨越模塊或庫邊界使用不同版本的 new 和 delete。
2.3、確保異常安全
自定義 operator new 應確保在分配失敗時拋出 std::bad_alloc,而不是返回 nullptr。
#include <new>
#include <iostream>void* operator new(size_t size) {if (size == 0) size = 1; // 確保非零分配void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc(); // 分配失敗時拋出異常return ptr;
}void operator delete(void* ptr) noexcept {std::free(ptr);
}
2.4、定義 placement new 和 delete
C++ 提供了 placement new,允許你在已分配的內存上構造對象。如果你需要自定義 operator new,也應該定義對應的 placement operator delete。
#include <iostream>class Widget {
public:static void* operator new(size_t size, void* location) {std::cout << "Placement new called" << std::endl;return location;}static void operator delete(void* ptr, void* location) {std::cout << "Placement delete called" << std::endl;// 不釋放內存,因為是 placement new}
};int main() {char buffer[sizeof(Widget)];/*下面這句會執行兩步:1、調用 operator new(size_t size, void* location),在執行這一步時,會將sizeof(Widget)作為第一個參數,buffer作為第2個參數2、調用對象的構造函數在分配的內存上初始化對象。*/Widget* w = new (buffer) Widget;w->~Widget(); // 顯式調用析構函數,輸出Placement new calledreturn 0;
}
3、特殊場景
- 定制小對象分配器,對于需要頻繁分配和釋放的小對象,可以實現更高效的內存池,這樣做可以優化的原因是,提前做好了分配內存的這一步,在獲取到內存后,只需要再調用構造函數就可以了。
#include <iostream>
#include <vector>class SmallObjectAllocator {
private:std::vector<void*> freeList;size_t objectSize;public:SmallObjectAllocator(size_t objSize) : objectSize(objSize) {}void* allocate() {if (freeList.empty()) {return std::malloc(objectSize);} else {void* ptr = freeList.back();freeList.pop_back();return ptr;}}void deallocate(void* ptr) {freeList.push_back(ptr);}
};class Widget {
public:static SmallObjectAllocator allocator;static void* operator new(size_t size) {return allocator.allocate();}static void operator delete(void* ptr) {allocator.deallocate(ptr);}
};SmallObjectAllocator Widget::allocator(sizeof(Widget));int main() {Widget* w1 = new Widget;Widget* w2 = new Widget;delete w1;delete w2;return 0;
}
- 優點,減少小對象分配的開銷,提升內存分配性能。
3、總結
在編寫 operator new 和 operator delete 時,應遵循以下關鍵點:
- 成對定義 new 和 delete,包括 placement 版本。
- 確保異常安全,分配失敗時拋出 std::bad_alloc。
- 遵循匹配的內存分配和釋放規則,避免跨模塊不一致。
- 在需要優化性能時,可實現自定義的內存池或分配器。