跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 智能指针:使用场景、实现原理与内存泄漏防治

C++ 智能指针基于 RAII 机制,通过对象生命周期自动管理动态资源,有效解决手动 new/delete 导致的内存泄漏问题。文章详细对比了 unique_ptr、shared_ptr 和 weak_ptr 的使用场景及底层实现原理,重点解析了 shared_ptr 的引用计数机制及循环引用问题的解决方案。通过代码示例展示了标准库智能指针的正确用法、定制删除器配置以及常见陷阱规避策略,帮助开发者构建更健壮的内存管理系统。

蓝绿部署发布于 2026/3/29更新于 2026/7/2133 浏览
C++ 智能指针:使用场景、实现原理与内存泄漏防治

C++ 智能指针:使用场景、实现原理与内存泄漏防治

一、智能指针的使用场景分析

C++ 以高效著称,手动内存管理(new/delete)避免了高级语言垃圾回收的开销,但这把双刃剑对程序员要求极高。手动管理极易导致内存泄漏,尤其是在异常处理场景中。

例如,在函数中连续分配资源时,若中间发生异常,后续的 delete 可能无法执行,导致资源泄露。如果为了捕获异常而嵌套 try-catch 块,代码会变得极其臃肿且难以维护。智能指针的出现正是为了解决这类问题,通过对象生命周期自动管理资源。

double Divide(int a, int b) {
    if (b == 0) throw "Divide by zero condition!";
    else return (double)a / (double)b;
}

void Func() {
    // 原始方案:若 Divide 抛出异常,array1 和 array2 未释放
    int* array1 = new int[10];
    int* array2 = new int[10];

    try {
        int len, time;
        cin >> len >> time;
        cout << Divide(len, time) << endl;
    } catch (...) {
        cout << "delete []" << array1 << endl;
        cout << "delete []" << array2 << endl;
        delete[] array1;
        delete[] array2;
        throw; // 重新抛出异常
    }

    // ... 后续逻辑
    delete[] array1;
    delete[] array2;
}

二、RAII 和智能指针的设计思路

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种利用对象生命周期管理资源的类设计思想。资源(内存、文件句柄、锁等)在构造时获取,在析构时释放。无论函数正常返回还是因异常退出栈帧,析构函数都会被调用,从而保证资源释放。

智能指针类除了满足 RAII,还需像迭代器一样重载运算符(operator*, operator->, operator[]),以便透明地访问底层资源。

template<class T>
class SmartPtr {
public:
    // RAII: 构造获取资源,析构释放资源
    SmartPtr(T* ptr) : _ptr(ptr) {}
    ~SmartPtr() {
        cout << "delete[] " << _ptr << endl;
        delete[] _ptr;
    }

    // 重载运算符,模拟指针行为
    T& operator*() { return *_ptr; }
    T* operator->() { return _ptr; }
    T& operator[](size_t i) { return _ptr[i]; }

private:
    T* _ptr;
};

void Func() {
    // 使用 RAII 智能指针后,无需手动 delete,异常也能安全释放
    SmartPtr<int> sp1(new int[10]);
    SmartPtr<int> sp2(new int[10]);
    for (size_t i = 0; i < 10; i++) {
        sp1[i] = sp2[i] = i;
    }
    // 即使 Divide 抛异常,sp1 和 sp2 析构时也会自动释放内存
    int len, time;
    cin >> len >> time;
    cout << Divide(len, time) << endl;
}

三、C++ 标准库智能指针的使用

C++ 标准库中的智能指针定义在 <memory> 头文件中。主要包括 unique_ptr、shared_ptr 和 weak_ptr。auto_ptr 是 C++98 遗留产物,因拷贝语义混乱已被废弃。

1. unique_ptr(独占所有权)

unique_ptr 翻译为唯一指针,不支持拷贝,仅支持移动(右值)。适用于不需要共享资源的场景,性能最高。

unique_ptr<Date> up1(new Date);
// unique_ptr<Date> up2(up1); // 错误:禁止拷贝
unique_ptr<Date> up3(move(up1)); // 正确:移动后 up1 悬空

2. shared_ptr(共享所有权)

shared_ptr 支持拷贝和移动,底层通过引用计数实现多指针共享同一资源。当引用计数归零时自动释放资源。

shared_ptr<Date> sp1(new Date);
shared_ptr<Date> sp2(sp1); // 引用计数 +1
shared_ptr<Date> sp3(sp2); // 引用计数 +1
cout << sp1.use_count() << endl; // 输出 3

优化建议: 推荐使用 make_shared。它会将控制块和资源分配在同一块内存中,减少内存碎片并提高缓存命中率。

shared_ptr<Date> sp1 = make_shared<Date>(2024, 9, 11);

3. weak_ptr(弱引用)

weak_ptr 不管理资源,不增加引用计数,专门用于解决 shared_ptr 的循环引用问题。需配合 lock() 方法使用。

4. 定制删除器

默认删除器使用 delete。对于 new[] 或 malloc 的资源,需指定匹配的删除器(如 delete[] 或 free)。

// 管理数组资源
unique_ptr<Date[]> up1(new Date[5]); 

// 自定义删除器示例(lambda)
auto delArrOBJ = [](Date* ptr) { delete[] ptr; };
unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);

四、智能指针的原理

1. unique_ptr 实现核心

禁用拷贝构造和赋值,仅允许移动。确保同一时刻只有一个指针拥有资源所有权。

template<class T>
class unique_ptr {
public:
    explicit unique_ptr(T* ptr) : _ptr(ptr) {}
    ~unique_ptr() { if (_ptr) { delete _ptr; _ptr = nullptr; } }

    // 禁用拷贝
    unique_ptr(const unique_ptr<T>&) = delete;
    unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;

    // 支持移动
    unique_ptr(unique_ptr<T>&& sp) : _ptr(sp._ptr) { sp._ptr = nullptr; }
    unique_ptr<T>& operator=(unique_ptr<T>&& sp) {
        delete _ptr; _ptr = sp._ptr; sp._ptr = nullptr; return *this;
    }
private:
    T* _ptr;
};

2. shared_ptr 实现核心

需要维护两份数据:资源本身和控制块(含引用计数和删除器)。

  • 构造函数:初始化资源指针和控制块计数器(初始为 1)。
  • 拷贝构造:复制指针,计数器 +1。
  • 析构/赋值:计数器 -1,若为 0 则释放资源和控制块。
template<class T>
class shared_ptr {
public:
    shared_ptr(T* ptr = nullptr) : _ptr(ptr), _pcount(new int(1)) {}

    shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr), _pcount(sp._pcount) {
        ++(*_pcount); // 拷贝时计数加一
    }

    ~shared_ptr() {
        release();
    }

    void release() {
        if (--(*_pcount) == 0) {
            _del(_ptr); // 使用删除器释放资源
            delete _pcount;
            _ptr = nullptr;
            _pcount = nullptr;
        }
    }

    // 赋值运算符重载
    shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
        if (_ptr != sp._ptr) {
            release();
            _ptr = sp._ptr;
            _pcount = sp._pcount;
            _del = sp._del;
            ++(*_pcount);
        }
        return *this;
    }

private:
    T* _ptr;
    int* _pcount;
    function<void(T*)> _del = [](T* ptr){ delete ptr; }; // 默认删除器
};

五、shared_ptr 和 weak_ptr

1. 循环引用问题

两个 shared_ptr 互相持有对方时,引用计数永远无法归零,导致内存泄漏。

struct ListNode {
    int _data;
    std::shared_ptr<ListNode> _next;
    std::shared_ptr<ListNode> _prev;
    ~ListNode() { cout << "~ListNode()" << endl; }
};

int main() {
    std::shared_ptr<ListNode> n1(new ListNode);
    std::shared_ptr<ListNode> n2(new ListNode);
    n1->_next = n2; // n2 引用计数变为 2
    n2->_prev = n1; // n1 引用计数变为 2
    // 析构时,两者计数均为 2,永远不会释放
    return 0;
}

2. 解决方案:weak_ptr

将其中一个方向改为 weak_ptr。weak_ptr 不增加引用计数,仅观察资源。当最后一个 shared_ptr 销毁时,资源被释放,weak_ptr 失效。

struct ListNode {
    std::weak_ptr<ListNode> _next;
    std::weak_ptr<ListNode> _prev;
    ~ListNode() { cout << "~ListNode()" << endl; }
};

int main() {
    std::shared_ptr<ListNode> n1(new ListNode);
    std::shared_ptr<ListNode> n2(new ListNode);
    n1->_next = n2; // n2 引用计数仍为 1
    n2->_prev = n1; // n1 引用计数仍为 1
    // 析构时,计数归零,资源正常释放
    return 0;
}

访问 weak_ptr 绑定的资源前,需调用 lock() 获取临时的 shared_ptr。若资源已释放,lock() 返回空指针。

六、内存泄漏

1. 概念与危害

内存泄漏指程序未能释放不再使用的内存。短期运行影响不大,但长期运行的服务(如操作系统、后台进程)会因可用内存减少而变慢甚至崩溃。

2. 如何避免

  1. 事前预防:优先使用智能指针(RAII 机制),遵循'谁申请谁释放'原则。
  2. 特殊场景:若必须手动管理,确保异常路径下也能释放资源。
  3. 事后检测:定期使用工具(如 Valgrind)扫描内存泄漏。

总结来说,现代 C++ 开发应尽量避免裸指针,利用智能指针体系保障内存安全。

目录

  1. C++ 智能指针:使用场景、实现原理与内存泄漏防治
  2. 一、智能指针的使用场景分析
  3. 二、RAII 和智能指针的设计思路
  4. 三、C++ 标准库智能指针的使用
  5. 1. unique_ptr(独占所有权)
  6. 2. shared_ptr(共享所有权)
  7. 3. weak_ptr(弱引用)
  8. 4. 定制删除器
  9. 四、智能指针的原理
  10. 1. unique_ptr 实现核心
  11. 2. shared_ptr 实现核心
  12. 五、sharedptr 和 weakptr
  13. 1. 循环引用问题
  14. 2. 解决方案:weak_ptr
  15. 六、内存泄漏
  16. 1. 概念与危害
  17. 2. 如何避免
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • GitHub Copilot 实战:AI 辅助编程效率提升指南
  • Django REST Framework 企业级 API 架构实战
  • 使用 Higress 将 REST API 转换为 MCP Server 工具
  • C 语言实现 AI 推理优化:量化、算子融合与内存映射
  • 英伟达 GTC 2026 大会:AI 智能体时代到来,发布新一代推理芯片与 Rubin 架构
  • AI 音频工具合集
  • Flutter 在 OpenHarmony 中应对跨域挑战:flutter_cors 实战方案
  • Beyond Compare 安装与 Git 集成配置指南
  • 初学者如何入门大语言模型开发
  • 大模型学习进阶五阶段:从入门到应用的路径解析
  • 基于微服务的智能家居物联网平台实践
  • QUEST 一体机 SideQuest 安装 APK 与 OBB 数据包教程
  • Whisper-WebUI 语音转文字工具实战指南
  • 全栈开发的演变:从 LAMP 到 MEAN 再到现代 JavaScript
  • DeepSeek 爆发期,前端工程师的转型路径与核心价值
  • 全员 DeepSeek 时代:前端开发范式变革与实践
  • LangGraph v0.1 正式发布:构建自定义认知架构的新工具
  • Python 开发常用库大全及分类介绍
  • 前端面试核心考点梳理:从基础原理到框架实战
  • 新机型 Copilot 键替代右 Ctrl 键的解决方案

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online