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。
- 遵循匹配的内存分配和释放规则,避免跨模块不一致。
- 在需要优化性能时,可实现自定义的内存池或分配器。