首页 > 后端开发 > C++ > 正文

如何正确使用new和delete操作符 动态内存分配与释放的最佳实践

P粉602998670
发布: 2025-08-13 17:24:02
原创
1091人浏览过

正确使用new和delete操作符的关键在于严格配对并区分单个对象与数组的分配,1. new用于动态内存分配,delete用于释放单个对象;2. new[]用于数组分配,delete[]用于释放数组;3. 释放后应将指针置为nullptr以避免悬空指针;4. 异常安全需特别注意,现代c++++推荐使用智能指针如std::unique_ptr和std::shared_ptr来自动管理资源,从而减少内存泄露和未定义行为的风险。

如何正确使用new和delete操作符 动态内存分配与释放的最佳实践

正确使用

new
登录后复制
delete
登录后复制
操作符,其核心在于理解它们是C++中用于在堆上进行动态内存分配与释放的底层工具。说白了,
new
登录后复制
负责找一块足够大的内存给你用,而
delete
登录后复制
则负责把这块内存还回去,避免浪费。关键是,你拿了就得还,而且要正确地还,不然就会出问题。

如何正确使用new和delete操作符 动态内存分配与释放的最佳实践

动态内存管理,尤其是在C++里,是个挺让人头疼的话题,因为它不像栈内存那样自动管理。你得亲力亲为,每次

new
登录后复制
一个对象或数组,就得记得用
delete
登录后复制
delete[]
登录后复制
来释放。这听起来简单,但实际项目里,一旦逻辑复杂起来,或者有异常抛出,就很容易忘掉,然后内存泄露就悄悄发生了。

解决方案

要正确使用

new
登录后复制
delete
登录后复制
,最基本的原则是:
new
登录后复制
delete
登录后复制
必须配对使用,并且要区分是分配单个对象还是数组。

如何正确使用new和delete操作符 动态内存分配与释放的最佳实践

分配单个对象:

// 分配一个 int 类型的内存,并用 10 初始化
int* p = new int(10); 
// 使用 p 指向的内存...
// 释放 p 指向的内存
delete p; 
p = nullptr; // 良好的习惯,防止悬空指针
登录后复制

分配对象数组:

如何正确使用new和delete操作符 动态内存分配与释放的最佳实践
// 分配一个包含 5 个 int 的数组
int* arr = new int[5]; 
// 使用 arr 指向的数组...
// 释放 arr 指向的数组
delete[] arr; // 注意这里是 delete[]
arr = nullptr;
登录后复制

关键点在于:

  1. 配对使用:
    new
    登录后复制
    对应
    delete
    登录后复制
    new[]
    登录后复制
    对应
    delete[]
    登录后复制
    。这是铁律,搞错了会导致未定义行为,轻则内存泄露,重则程序崩溃。
  2. 释放后置空: 这是一个好习惯。当内存被
    delete
    登录后复制
    后,指针仍然可能指向那块已经无效的内存区域,成为“悬空指针”。将其置为
    nullptr
    登录后复制
    可以有效避免后续误用。
  3. 异常安全: 这是最容易被忽略的。如果在
    new
    登录后复制
    delete
    登录后复制
    之间发生异常,
    delete
    登录后复制
    可能永远不会被调用到,从而导致内存泄露。现代C++更倾向于使用RAII(Resource Acquisition Is Initialization)原则,即通过对象生命周期来管理资源,最典型的就是智能指针。

为什么
new
登录后复制
delete
登录后复制
用不好会出大问题?

这话说起来,其实就是内存管理里的那些老生常谈,但每次看到有经验的开发者在这上面翻车,都觉得有必要再强调一下。用不好

new
登录后复制
delete
登录后复制
,最直接的后果就是内存泄露(memory leak)和各种未定义行为。

内存泄露:这是最常见的问题。你

new
登录后复制
了一块内存,用完了,但是忘了
delete
登录后复制
它。这块内存就一直被你的程序“霸占”着,操作系统以为它还在被使用,所以不会回收。如果你的程序运行时间长,或者频繁进行这种操作,内存就会被慢慢耗尽,最终导致程序变慢,甚至系统崩溃。想象一下,你借了图书馆的书,看完了不还,那书架上的书只会越来越少,其他人就没得借了。

双重释放(Double Free):你对同一块内存调用了两次

delete
登录后复制
。这通常会导致程序崩溃,因为你试图释放一块已经被释放的内存。操作系统会很困惑,或者发现这块内存已经被别人拿走了,就会报错。

int* p = new int;
delete p;
delete p; // 错误!双重释放
登录后复制

悬空指针(Dangling Pointer):内存被

delete
登录后复制
后,指针本身并没有消失,它仍然指向那块现在已经无效的内存区域。如果你之后不小心通过这个悬空指针去访问那块内存,就会导致未定义行为。可能读到垃圾数据,可能写到不该写的地方,结果难以预测。

int* p = new int(10);
delete p;
// 此时 p 是悬空指针
*p = 20; // 错误!访问已释放的内存
登录后复制

数组与非数组的混用

new int
登录后复制
new int[N]
登录后复制
是不同的,它们分配的内存结构可能不一样,
delete
登录后复制
delete[]
登录后复制
也必须严格匹配。
delete[]
登录后复制
知道如何调用数组中每个元素的析构函数,并释放整个数组的内存。而
delete
登录后复制
只知道释放单个对象的内存。如果你用
delete
登录后复制
去释放
new[]
登录后复制
出来的数组,只有第一个元素的析构函数会被调用(如果是非POD类型),并且内存可能无法完全释放,导致部分泄露。反之,用
delete[]
登录后复制
去释放
new
登录后复制
出来的单个对象,也是未定义行为。

int* single = new int(5);
delete[] single; // 错误!用 delete[] 释放单个对象

int* arr = new int[5];
delete arr; // 错误!用 delete 释放数组
登录后复制

这些问题,说白了都是因为我们作为程序员,需要手动管理内存的生命周期,而人总有犯错的时候。

智能指针如何彻底改变动态内存管理?

说真的,自从C++11引入了智能指针,我对动态内存管理的看法彻底变了。以前用

new
登录后复制
delete
登录后复制
,总得小心翼翼,生怕漏了哪个
delete
登录后复制
。但智能指针这东西,简直就是内存管理的“救星”,它把RAII原则发挥到了极致。

RAII(Resource Acquisition Is Initialization)的核心思想是:把资源(比如内存)的生命周期和对象的生命周期绑定起来。当对象被创建时,资源被获取;当对象被销毁时(比如超出作用域),资源被自动释放。这样一来,你就不需要手动去调用

delete
登录后复制
了,因为析构函数会自动帮你完成这些事。

C++标准库提供了几种智能指针:

  1. std::unique_ptr
    登录后复制
    : 这是最推荐的智能指针,它表示独占所有权。一个
    unique_ptr
    登录后复制
    只能指向一个资源,并且不能被复制,但可以被移动。这意味着资源的所有权可以在不同
    unique_ptr
    登录后复制
    之间转移。当
    unique_ptr
    登录后复制
    超出作用域时,它所管理的内存会自动被释放。

    #include <memory>
    #include <iostream>
    
    class MyObject {
    public:
        MyObject() { std::cout << "MyObject created\n"; }
        ~MyObject() { std::cout << "MyObject destroyed\n"; }
        void doSomething() { std::cout << "Doing something...\n"; }
    };
    
    void processUniqueObject() {
        // 使用 std::make_unique 替代 new,更安全高效
        std::unique_ptr<MyObject> objPtr = std::make_unique<MyObject>();
        objPtr->doSomething();
        // objPtr 在这里超出作用域,MyObject 会自动被销毁
    } // 析构函数自动调用,内存自动释放
    
    void transferOwnership() {
        std::unique_ptr<MyObject> p1 = std::make_unique<MyObject>();
        std::unique_ptr<MyObject> p2 = std::move(p1); // 所有权从 p1 转移到 p2
        // 此时 p1 变为空
        if (!p1) {
            std::cout << "p1 is now empty.\n";
        }
        p2->doSomething();
        // p2 超出作用域时,MyObject 销毁
    }
    登录后复制

    std::unique_ptr
    登录后复制
    是默认首选,因为它没有
    shared_ptr
    登录后复制
    的引用计数开销,更轻量。

  2. std::shared_ptr
    登录后复制
    : 它表示共享所有权。多个
    shared_ptr
    登录后复制
    可以共同管理同一个资源。
    shared_ptr
    登录后复制
    内部有一个引用计数器,每当一个
    shared_ptr
    登录后复制
    指向资源时,计数器加一;每当一个
    shared_ptr
    登录后复制
    不再指向资源时,计数器减一。当引用计数变为零时,资源才会被释放。

    #include <memory>
    #include <iostream>
    
    // MyObject 和上面一样
    
    void processSharedObject() {
        std::shared_ptr<MyObject> objPtr1 = std::make_shared<MyObject>(); // 引用计数为 1
        std::cout << "Count after objPtr1 creation: " << objPtr1.use_count() << "\n";
    
        {
            std::shared_ptr<MyObject> objPtr2 = objPtr1; // 引用计数为 2
            std::cout << "Count after objPtr2 copy: " << objPtr1.use_count() << "\n";
            objPtr2->doSomething();
        } // objPtr2 超出作用域,引用计数减 1 (变为 1)
        std::cout << "Count after objPtr2 out of scope: " << objPtr1.use_count() << "\n";
    
        // objPtr1 在这里超出作用域,引用计数减 1 (变为 0),MyObject 会自动被销毁
    } // 析构函数自动调用,内存自动释放
    登录后复制

    std::shared_ptr
    登录后复制
    适合于需要共享资源所有权的场景,但要注意循环引用问题,这可能导致内存泄露(可以使用
    std::weak_ptr
    登录后复制
    来解决)。

智能指针的出现,极大简化了C++的内存管理,让程序员可以更专注于业务逻辑,而不是费心去追踪每一块内存的生命周期。它们几乎消除了手动

delete
登录后复制
的必要性,显著减少了内存泄露和悬空指针的风险。

那么,
new
登录后复制
delete
登录后复制
在现代C++中还有用武之地吗?

这个问题问得好,因为智能指针确实是主流,但

new
登录后复制
delete
登录后复制
并没有完全消失。它们仍然是C++的底层基石,在某些特定场景下,你还是会用到它们。

  1. 实现自定义内存管理: 如果你在开发一个高性能的系统,需要对内存分配有极致的控制,比如实现一个内存池(memory pool)或者自定义的分配器(allocator),那么你就需要直接使用

    new
    登录后复制
    delete
    登录后复制
    来从操作系统获取和释放原始内存块。智能指针本身也是基于
    new
    登录后复制
    delete
    登录后复制
    (或者说更底层的
    operator new
    登录后复制
    operator delete
    登录后复制
    )来实现的。

  2. 与C语言API交互: C语言没有智能指针的概念,很多C库函数会返回通过

    malloc
    登录后复制
    分配的内存,或者要求你传入一个指针,由它们来填充数据。在这种情况下,你可能需要使用
    new
    登录后复制
    来分配内存,然后把原始指针传给C函数,或者接收C函数返回的原始指针,再手动
    delete
    登录后复制
    (或者更常见的,用
    free
    登录后复制
    ,这取决于分配方式)。

    // 假设有一个 C 函数返回 malloc 分配的字符串
    // char* get_c_string(); 
    // char* s = get_c_string();
    // // 使用 s
    // free(s); // 注意这里用 free,因为是 malloc 分配的
    登录后复制

    或者,你可能需要将

    new
    登录后复制
    出来的对象指针传递给一个期望原始指针的C函数。

  3. 在某些特定容器或数据结构中: 虽然标准库容器已经非常强大,但在实现一些非常规的、对内存布局有特殊要求的数据结构时,你可能需要手动控制内存分配。比如,实现一个侵入式链表,或者一个需要紧凑内存布局的图结构。

  4. Placement New: 这是一个比较高级的用法,允许你在已经分配好的内存上构造对象。这意味着你先用

    new char[N]
    登录后复制
    malloc
    登录后复制
    分配一块原始内存,然后用
    placement new
    登录后复制
    在这块内存上“放置”一个对象。这在内存池或者需要精确控制对象构造位置的场景下很有用。

    #include <new> // 包含 placement new
    #include <iostream>
    
    class MyClass {
    public:
        MyClass() { std::cout << "MyClass constructed.\n"; }
        ~MyClass() { std::cout << "MyClass destructed.\n"; }
    };
    
    char buffer[sizeof(MyClass)]; // 预先分配一块内存
    
    int main() {
        MyClass* obj = new (buffer) MyClass(); // 在 buffer 上构造 MyClass 对象
        // 使用 obj...
        obj->~MyClass(); // 手动调用析构函数
        // 不需要 delete buffer,因为 buffer 是栈上的数组
        return 0;
    }
    登录后复制

    这里需要注意的是,

    placement new
    登录后复制
    只负责构造对象,不负责分配内存。对应的,你也不能用
    delete
    登录后复制
    来释放它,而需要手动调用对象的析构函数。

总的来说,在现代C++日常开发中,我们应该优先使用智能指针(

std::unique_ptr
登录后复制
std::shared_ptr
登录后复制
,配合
std::make_unique
登录后复制
std::make_shared
登录后复制
)。它们提供了更安全、更简洁的内存管理方式。只有当你确实需要底层控制,并且清楚自己在做什么时,才应该考虑直接使用
new
登录后复制
delete
登录后复制
。它们是强大的工具,但需要你承担起全部的责任。

以上就是如何正确使用new和delete操作符 动态内存分配与释放的最佳实践的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号