在 C++ 里,很多内存问题并不是出在“不会申请”,而是出在“释放得不对”。`delete` 看起来只是一个简单运算符,但一旦和 `new`、数组分配、对象生命周期、所有权混在一起,就很容易出现内存泄漏、悬空指针甚至未定义行为。
这篇文章按“先理解规则,再判断场景,最后收敛到更安全写法”的顺序展开:先说明 `delete` 和 `delete[]` 到底分别对应什么,再对比栈、堆与智能指针的内存管理方式,最后总结安全删除的关键原则。读完后,你可以快速判断一段代码该不该手动 `delete`,以及什么时候应该直接换成智能指针或 RAII。
先弄清楚:`delete` 到底释放什么
内存管理为什么会牵涉到 `delete`
在 C++ 中,内存管理直接影响程序性能与稳定性。`delete` 运算符的核心作用,是释放此前通过 new 分配的动态内存,避免对象不再使用后仍长期占用堆空间。

`delete` 与 `delete[]` 的区别
`delete` 只用于释放单个对象;如果内存是通过 new[] 分配得到的数组,就必须使用 delete[]。两者对应关系不能混用。
delete pointer;
delete[] array_pointer;
单对象和数组的释放示例
class MyClass {
public:
MyClass() { std::cout << "Constructor called" << std::endl; }
~MyClass() { std::cout << "Destructor called" << std::endl; }
};
int main() {
// 单个对象分配
MyClass* singleObj = new MyClass();
delete singleObj;
// 数组分配
MyClass* arrayObj = new MyClass[5];
delete[] arrayObj;
return 0;
}
必须记住的基础规则
| 原则 | 描述 |
|---|---|
| 匹配分配 | 始终对用 new 分配的对象使用 delete |
| 数组处理 | 对用 new[] 分配的数组使用 delete[] |
| 空指针检查 | 删除前检查空指针 |
最常见的几个坑
围绕 `delete` 的错误,大多集中在下面几类:
- 双重删除
- 删除已经删除过的指针
- 忘记释放动态分配的内存
如果一块通过 new 得到的内存没有被正确释放,结果通常就是内存泄漏;如果释放方式不匹配,问题会更隐蔽,也更难排查。
除了手动释放,还要先判断分配方式
为什么要区分栈、堆和智能指针
讨论 `delete` 之前,先看对象是怎么分配出来的。因为不是所有对象都需要手动释放:栈对象由系统自动管理,堆对象才需要开发者显式处理,而智能指针则把这项工作交还给语言工具。

栈分配和堆分配有什么不同
栈分配
void stackAllocation() {
int localVariable = 42; // 自动管理
}
栈上对象会在离开作用域时自动销毁,通常不需要、也不能手动 `delete`。
堆分配
void heapAllocation() {
int* dynamicVariable = new int(42); // 手动内存管理
delete dynamicVariable;
}
堆对象生命周期由程序员控制,这也是 `delete` 真正发挥作用的典型场景。
三种分配模式怎么选
| 模式 | 分配方式 | 释放方式 | 生命周期 | 性能 |
|---|---|---|---|---|
| 栈 | 自动 | 自动 | 函数作用域 | 快速 |
| 堆 | 手动 | 手动 | 程序员控制 | 灵活 |
| 智能指针 | 自动 | 自动 | 基于作用域 | 高效 |
现代 C++ 更常见的做法:智能指针
`std::unique_ptr`
#include <memory>
void uniquePointerExample() {
std::unique_ptr<int> uniqueInt(new int(100));
// 超出作用域时自动删除
}
`std::shared_ptr`
#include <memory>
void sharedPointerExample() {
std::shared_ptr<int> sharedInt = std::make_shared<int>(200);
// 引用计数,自动清理
}
如果代码可以不直接接触裸指针,优先使用智能指针通常更稳妥。这样既能减少手写 `delete` 的次数,也能让所有权关系更清晰。
需要更细控制时:自定义内存池
在某些强调性能或批量管理资源的场景里,项目可能会引入自定义内存池,将分配和释放统一管理:
class MemoryPool {
private:
std::vector<int*> allocatedMemory;
public:
int* allocate() {
int* memory = new int;
allocatedMemory.push_back(memory);
return memory;
}
void deallocateAll() {
for (auto ptr : allocatedMemory) {
delete ptr;
}
allocatedMemory.clear();
}
};
这类方案能集中控制释放时机,但也意味着你要更严谨地维护所有权和清理逻辑。
怎样把 `delete` 用得更安全
为什么“能删掉”不等于“删得安全”
安全删除的目标不只是释放内存,还包括避免未定义行为、降低悬空指针风险,并让资源生命周期更容易推断。手动 `delete` 的问题往往不在语法,而在上下文。

空指针检查与删除后置空
void safeDelete(int* ptr) {
if (ptr != nullptr) {
delete ptr;
ptr = nullptr; // 防止悬空指针
}
}
这一写法体现了两个思路:删除前先确认指针可用,删除后尽量避免留下悬空引用。需要注意的是,上面这个函数参数按值传递,函数内部把 ptr 设为 nullptr,并不会同步修改调用方手里的原始指针;它更适合表达“删除后应及时置空”的原则,而不是作为完整封装直接照搬。
用智能指针代替手动删除
独占资源管理
#include <memory>
class ResourceManager {
private:
std::unique_ptr<int> resource;
public:
ResourceManager() {
resource = std::make_unique<int>(42);
}
// 对象超出作用域时自动安全删除
};
共享资源管理
std::shared_ptr<int> createSafeResource() {
return std::make_shared<int>(100);
}
不同删除方式的取舍
| 技术 | 安全级别 | 开销 | 复杂度 |
|---|---|---|---|
| 原始指针 | 低 | 最小 | 手动 |
| 独占指针 | 高 | 低 | 自动 |
| 共享指针 | 高 | 中等 | 引用计数 |
| 自定义删除器 | 灵活 | 可变 | 高级 |
需要特殊释放逻辑时,用自定义删除器
class CustomDeleter {
public:
void operator()(int* ptr) {
std::cout << "自定义删除" << std::endl;
delete ptr;
}
};
void customDeleterExample() {
std::unique_ptr<int, CustomDeleter> customPtr(new int(200));
// 使用自定义逻辑自动安全删除
}
当资源不只是普通堆对象,而是文件句柄、套接字或需要额外清理动作的对象时,自定义删除器会更有价值。
RAII 仍然是最稳的资源管理原则
class ResourceWrapper {
private:
int* resource;
public:
ResourceWrapper() : resource(new int(50)) {}
~ResourceWrapper() {
delete resource; // 自动安全删除
}
};
RAII(资源获取即初始化)的核心思想,是把资源生命周期绑定到对象生命周期。对象构造时拿到资源,析构时自动释放,这也是现代 C++ 管理资源最基础、最可靠的思路之一。
实际开发里,优先遵循这几条
- 始终让
new与正确的delete或delete[]配对 - 能用栈对象时,优先用栈对象
- 涉及动态内存时,优先考虑
std::unique_ptr和std::shared_ptr - 避免双重删除、重复释放和所有权不清晰的裸指针传递
- 需要手动管理时,明确资源生命周期,并在删除后及时处理悬空指针问题
总结
掌握 `delete` 运算符,本质上是在掌握 C++ 里的对象生命周期和资源所有权。真正可靠的写法,不只是记住 `delete` 与 `delete[]` 的语法差异,更是尽量减少手动释放的机会,把资源管理交给作用域、RAII 和智能指针。
如果一段代码必须直接操作裸指针,那么最重要的判断标准就是:这块内存是谁申请的、谁负责释放、释放方式是否匹配,以及释放之后是否还可能被继续访问。把这几个问题想清楚,很多内存错误在写代码时就能提前避免。













