More Effective C++ 条款08:理解各种不同意义的new和delete
核心思想 :C++中的"new"和"delete"有多种不同含义,包括new操作符、operator new函数、placement new以及对应的delete形式。理解这些不同形式的区别和适用场景对于正确管理内存和避免资源泄漏至关重要。
🚀 1. 问题本质分析
1.1 new的多种含义:
- new操作符(new operator):语言内置的操作符,完成内存分配和对象构造
- operator new函数:只分配内存,不调用构造函数
- placement new:在已分配的内存上构造对象
1.2 delete的对应形式:
- delete操作符(delete operator):调用析构函数并释放内存
- operator delete函数:只释放内存,不调用析构函数
- placement delete:与placement new对应,只调用析构函数不释放内存
cpp
// 不同形式的new和delete示例
class MyClass {
public:
MyClass() { std::cout << "Constructor\n"; }
~MyClass() { std::cout << "Destructor\n"; }
// 自定义operator new
static void* operator new(size_t size) {
std::cout << "Custom operator new, size: " << size << "\n";
return ::operator new(size);
}
// 自定义operator delete
static void operator delete(void* ptr) {
std::cout << "Custom operator delete\n";
::operator delete(ptr);
}
};
// 使用示例
void demonstrateNews() {
// 1. new操作符:分配内存并构造对象
MyClass* obj1 = new MyClass; // 调用自定义operator new,然后构造函数
// 2. operator new函数:只分配内存
void* rawMemory = MyClass::operator new(sizeof(MyClass)); // 只分配,不构造
// 3. placement new:在已分配内存上构造对象
MyClass* obj2 = new(rawMemory) MyClass; // 在rawMemory上构造对象
// 对应的删除操作
delete obj1; // 调用析构函数并释放内存
obj2->~MyClass(); // 只调用析构函数(placement delete的替代)
MyClass::operator delete(rawMemory); // 只释放内存
}
📦 2. 问题深度解析
2.1 各种new/delete形式的对比:
形式 | 功能 | 是否调用构造函数/析构函数 | 是否分配/释放内存 |
---|---|---|---|
new操作符 | 分配内存并构造对象 | 是 | 是 |
operator new | 只分配内存 | 否 | 是 |
placement new | 在指定内存位置构造对象 | 是 | 否 |
delete操作符 | 调用析构函数并释放内存 | 是 | 是 |
operator delete | 只释放内存 | 否 | 是 |
placement delete | 调用析构函数但不释放内存 | 是 | 否 |
2.2 常见误用和问题:
cpp
// ❌ 错误的使用示例
class Problematic {
public:
Problematic() { buffer = new char[100]; }
~Problematic() { delete[] buffer; }
private:
char* buffer;
};
void demonstrateProblems() {
// 1. 错误:混用new操作符和operator delete
Problematic* obj = new Problematic;
// ::operator delete(obj); // ❌ 错误:不会调用析构函数,导致buffer泄漏
// 2. 错误:混用operator new和delete操作符
void* memory = ::operator new(sizeof(Problematic));
Problematic* obj2 = static_cast<Problematic*>(memory);
// delete obj2; // ❌ 错误:对未构造的对象调用析构函数,未定义行为
// 3. 错误:错误的placement delete使用
Problematic* obj3 = new(memory) Problematic;
// ::operator delete(obj3); // ❌ 错误:不会调用析构函数,导致buffer泄漏
obj3->~Problematic(); // ✅ 正确:先调用析构函数
::operator delete(memory); // ✅ 正确:再释放内存
}
⚖️ 3. 解决方案与最佳实践
3.1 正确使用模式:
cpp
// ✅ 正确的内存管理模式
class CorrectUsage {
public:
CorrectUsage() { data = new int[100]; }
~CorrectUsage() { delete[] data; }
// 自定义operator new(可选)
static void* operator new(size_t size) {
std::cout << "Allocating " << size << " bytes\n";
return ::operator new(size);
}
// 自定义operator delete(可选)
static void operator delete(void* ptr) {
std::cout << "Freeing memory\n";
::operator delete(ptr);
}
// 自定义placement new(高级用法)
static void* operator new(size_t size, void* location) {
return location; // 简单地返回指定位置
}
// 对应的placement delete(异常安全所需)
static void operator delete(void* ptr, void* location) {
// 通常为空实现,只在构造函数抛出异常时被调用
}
private:
int* data;
};
void demonstrateCorrectUsage() {
// 1. 正常使用new/delete
CorrectUsage* obj1 = new CorrectUsage;
delete obj1;
// 2. 使用operator new/delete进行低级内存管理
void* rawMem = CorrectUsage::operator new(sizeof(CorrectUsage));
CorrectUsage* obj2 = new(rawMem) CorrectUsage; // placement new
obj2->~CorrectUsage(); // 显式调用析构函数
CorrectUsage::operator delete(rawMem); // 释放内存
// 3. 数组版本的new/delete
CorrectUsage* array = new CorrectUsage[5];
delete[] array;
}
3.2 高级应用:自定义内存管理:
cpp
// 内存池实现示例
class MemoryPool {
public:
MemoryPool(size_t objectSize, size_t chunkSize = 100)
: objectSize_(objectSize), chunkSize_(chunkSize) {
allocateChunk();
}
~MemoryPool() {
for (void* chunk : chunks_) {
::operator delete(chunk);
}
}
void* allocate() {
if (freeList_ == nullptr) {
allocateChunk();
}
void* block = freeList_;
freeList_ = *static_cast<void**>(freeList_);
return block;
}
void deallocate(void* block) {
*static_cast<void**>(block) = freeList_;
freeList_ = block;
}
private:
void allocateChunk() {
void* chunk = ::operator new(chunkSize_ * objectSize_);
chunks_.push_back(chunk);
// 构建空闲链表
char* p = static_cast<char*>(chunk);
for (size_t i = 0; i < chunkSize_ - 1; ++i) {
*reinterpret_cast<void**>(p) = p + objectSize_;
p += objectSize_;
}
*reinterpret_cast<void**>(p) = freeList_;
freeList_ = chunk;
}
size_t objectSize_;
size_t chunkSize_;
void* freeList_ = nullptr;
std::vector<void*> chunks_;
};
// 使用自定义内存池的类
class PooledObject {
public:
static void* operator new(size_t size) {
if (size != sizeof(PooledObject)) {
return ::operator new(size);
}
return pool_.allocate();
}
static void operator delete(void* ptr) {
if (ptr == nullptr) return;
pool_.deallocate(ptr);
}
private:
static MemoryPool pool_;
int data[100];
};
MemoryPool PooledObject::pool_(sizeof(PooledObject));
3.3 现代C++增强:
cpp
// 使用alignas和aligned_alloc(C++17)
class AlignedObject {
public:
AlignedObject() = default;
static void* operator new(size_t size) {
// 要求64字节对齐
constexpr size_t alignment = 64;
if (size != sizeof(AlignedObject)) {
return ::operator new(size);
}
#ifdef _WIN32
return _aligned_malloc(size, alignment);
#else
return aligned_alloc(alignment, size);
#endif
}
static void operator delete(void* ptr) {
if (ptr == nullptr) return;
#ifdef _WIN32
_aligned_free(ptr);
#else
free(ptr);
#endif
}
private:
alignas(64) int data[100]; // 确保对象本身也是64字节对齐
};
// 使用std::pmr::memory_resource(C++17)
#include <memory_resource>
class PmrObject {
public:
explicit PmrObject(std::pmr::memory_resource* mr = nullptr) {
if (mr == nullptr) {
mr = std::pmr::get_default_resource();
}
data = mr->allocate(100 * sizeof(int));
}
~PmrObject() {
auto mr = std::pmr::get_default_resource();
mr->deallocate(data, 100 * sizeof(int));
}
// 使用placement new和自定义删除器
static void* operator new(size_t size, std::pmr::memory_resource* mr) {
return mr->allocate(size);
}
static void operator delete(void* ptr, std::pmr::memory_resource* mr) {
mr->deallocate(ptr, sizeof(PmrObject));
}
// 常规的operator delete
static void operator delete(void* ptr) {
auto mr = std::pmr::get_default_resource();
mr->deallocate(ptr, sizeof(PmrObject));
}
private:
void* data;
};
💡 关键实践原则
-
区分new操作符和operator new
理解它们的不同职责:
cpp// new操作符:分配内存+构造对象 MyClass* obj = new MyClass; // operator new:只分配内存 void* memory = MyClass::operator new(sizeof(MyClass));
-
配对使用new/delete形式
确保分配和释放方式匹配:
cpp// 匹配的new/delete对 MyClass* obj = new MyClass; // 使用new操作符 delete obj; // 使用delete操作符 void* mem = ::operator new(100); // 使用operator new ::operator delete(mem); // 使用operator delete MyClass* p = new(buffer) MyClass; // 使用placement new p->~MyClass(); // 手动调用析构函数(placement delete的替代)
-
为placement new提供对应的placement delete
确保异常安全:
cppclass ExceptionSafe { public: // placement new static void* operator new(size_t size, void* location) { return location; } // 对应的placement delete(在构造函数抛出异常时调用) static void operator delete(void* ptr, void* location) { // 清理资源,但不释放内存(因为内存不是我们分配的) } };
-
重载operator new/delete要小心
遵循惯例和规范:
cppclass CustomAllocator { public: // 常规operator new static void* operator new(size_t size) { if (size != sizeof(CustomAllocator)) { return ::operator new(size); // 委托给全局版本 } return customAllocate(size); } // 对应的operator delete static void operator delete(void* ptr) { if (ptr == nullptr) return; customDeallocate(ptr); } // 数组版本 static void* operator new[](size_t size) { return customAllocate(size); } static void operator delete[](void* ptr) { if (ptr == nullptr) return; customDeallocate(ptr); } };
现代C++增强:
cpp// 使用noexcept规范 class NoExceptAllocator { public: static void* operator new(size_t size) noexcept { try { return ::operator new(size); } catch (...) { return nullptr; // 返回nullptr而不是抛出异常 } } static void operator delete(void* ptr) noexcept { ::operator delete(ptr); } }; // 使用constexpr分配(C++20) struct ConstexprAllocator { constexpr static void* operator new(size_t size) { // 在编译时分配内存(如果支持) // 注意:这需要编译器支持 return nullptr; // 伪实现 } constexpr static void operator delete(void* ptr) { // 编译时释放 } }; // 使用分配器感知设计 template<typename Allocator = std::allocator<MyType>> class AllocatorAware { public: using allocator_type = Allocator; explicit AllocatorAware(const Allocator& alloc = Allocator()) : allocator_(alloc) {} // 使用分配器分配内存 void* allocate(size_t n) { return allocator_.allocate(n); } void deallocate(void* p, size_t n) { allocator_.deallocate(static_cast<value_type*>(p), n); } private: Allocator allocator_; };
代码审查要点:
- 检查new/delete是否正确配对使用
- 确认自定义operator new/delete是否正确处理大小和边界情况
- 验证placement new是否有对应的placement delete用于异常安全
- 检查内存分配是否考虑了对齐要求
- 确认资源管理类是否正确实现所有必要的new/delete形式
总结:
C++提供了多种形式的new和delete,每种都有特定的用途和语义。new操作符负责分配内存和构造对象,operator new只分配内存,placement new在指定位置构造对象。对应的delete形式需要正确配对使用以确保资源正确释放。理解这些不同形式的区别对于实现高效、安全的内存管理至关重要。在现代C++中,可以利用内存池、自定义分配器、对齐分配等高级技术来优化内存使用,同时需要确保异常安全和资源管理的正确性。正确使用各种new和delete形式是编写高质量C++代码的基础。