C++ 中如何正确使用 delete 运算符

巷口听风
巷口听风
发布时间:2026-08-22 | 163人浏览过
原创

前言

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

在 C++ 里,很多内存问题并不是出在“不会申请”,而是出在“释放得不对”。`delete` 看起来只是一个简单运算符,但一旦和 `new`、数组分配、对象生命周期、所有权混在一起,就很容易出现内存泄漏、悬空指针甚至未定义行为。

这篇文章按“先理解规则,再判断场景,最后收敛到更安全写法”的顺序展开:先说明 `delete` 和 `delete[]` 到底分别对应什么,再对比栈、堆与智能指针的内存管理方式,最后总结安全删除的关键原则。读完后,你可以快速判断一段代码该不该手动 `delete`,以及什么时候应该直接换成智能指针或 RAII。

先弄清楚:`delete` 到底释放什么

内存管理为什么会牵涉到 `delete`

在 C++ 中,内存管理直接影响程序性能与稳定性。`delete` 运算符的核心作用,是释放此前通过 new 分配的动态内存,避免对象不再使用后仍长期占用堆空间。

展示单对象与数组释放方式区别,以及错误匹配带来的典型后果。
`delete` 与 `delete[]`把 `delete` 和 `delete[]` 对应关系画清楚,比死记语法更能减少误用。

`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` 之前,先看对象是怎么分配出来的。因为不是所有对象都需要手动释放:栈对象由系统自动管理,堆对象才需要开发者显式处理,而智能指针则把这项工作交还给语言工具。

展示栈、堆、智能指针三种内存分配模式在生命周期、释放责任和适用场景上的差异。
栈、堆与智能指针的内存管理差异先分清对象在哪分配,再决定是否需要自己写 `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` 的问题往往不在语法,而在上下文。

展示安全删除的核心策略,包括空指针检查、智能指针、RAII 与自定义删除器的适用关系。
安全删除的四种常见策略安全删除不是多写一个 `if`,而是把所有权和生命周期设计清楚。

空指针检查与删除后置空

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 和智能指针。

如果一段代码必须直接操作裸指针,那么最重要的判断标准就是:这块内存是谁申请的、谁负责释放、释放方式是否匹配,以及释放之后是否还可能被继续访问。把这几个问题想清楚,很多内存错误在写代码时就能提前避免。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表