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

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

C++ 智能指针基于 RAII 机制自动管理动态资源,解决手动内存分配易导致的泄漏问题。内容涵盖智能指针的使用场景分析,对比 auto_ptr、unique_ptr、shared_ptr 及 weak_ptr 特性。深入解析引用计数原理、循环引用成因与弱指针解决方案,并提供核心源码模拟实现。结合定制删除器与 make_shared 优化建议,指导开发者编写安全高效的 C++ 代码。

Pythonist发布于 2026/3/21更新于 2026/8/2136 浏览
C++ 智能指针:使用场景、实现原理与内存泄漏防治

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

C++ 作为公认的高效编程语言,其手动内存管理(new/delete)避免了高级语言(如 Java、Python)自动垃圾回收带来的额外开销。但这把双刃剑对程序员提出了更高要求,手动管理极易引发内存泄漏。

在某些复杂场景中,异常处理会导致资源释放逻辑遗漏。例如,在函数中连续分配内存后执行可能抛出异常的除法操作,若第二个 new 抛出异常,第一个分配的内存将因后续 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() {
    // 如果发生除 0 错误抛出异常,下面的 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; // 异常重新抛出
    }
    
    cout << "delete []" << array1 << endl;
    delete[] array1; 
    cout << "delete []" << array2 << endl;
    delete[] array2;
}

int main() {
    try {
        func();
    } catch (const char* errmsg) { cout << errmsg << endl; }
    catch (const exception& e) { cout << e.what() << endl; }
    catch (...) { cout << "未知异常" << endl; }
    return 0;
}

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

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种利用对象生命周期来管理动态资源的设计思想。资源可以是内存、文件句柄、网络连接或互斥锁等。在 RAII 模式下,资源被委托给一个对象管理,只要对象存在,资源就有效;当对象离开作用域(无论是正常退出还是异常退出),析构函数都会自动调用以释放资源。

智能指针类除了满足 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 的智能指针类管理 new 出来的数组以后,程序简单多了
    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;
    }
    int len, time; cin >> len >> time; 
    cout << divide(len, time) << endl;
    // 即使这里抛出异常,sp1 和 sp2 的析构函数也会自动清理内存
}

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

C++ 标准库中的智能指针都在 <memory> 头文件中。主要有以下几种:

  1. auto_ptr:C++98 时期的产物。由于拷贝会导致所有权转移(原指针置空),容易造成悬空指针访问,C++11 后强烈建议弃用。
  2. unique_ptr:C++11 引入,独占资源所有权。不支持拷贝,仅支持移动语义。适用于不需要共享资源的场景,性能最优。
  3. shared_ptr:共享资源所有权,底层采用引用计数。支持拷贝和移动。当引用计数降为 0 时自动释放资源,避免双重释放和泄漏。
  4. weak_ptr:不管理资源所有权,不增加引用计数。主要用于解决 shared_ptr 的循环引用问题。
struct Date {
    int _year, _month, _day;
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day) {}
    ~Date() { cout << "~Date()" << endl; }
};

int main() {
    // auto_ptr 已废弃,仅作了解
    // auto_ptr<Date> ap1(new Date);
    // auto_ptr<Date> ap2(ap1); // 拷贝后 ap1 变为 nullptr

    // unique_ptr 不支持拷贝,支持移动
    unique_ptr<Date> up1(new Date);
    unique_ptr<Date> up3(move(up1)); // 移动后 up1 悬空

    // shared_ptr 支持拷贝,引用计数增加
    shared_ptr<Date> sp1(new Date);
    shared_ptr<Date> sp2(sp1);
    shared_ptr<Date> sp3(sp2);
    cout << sp1.use_count() << endl; // 输出 3

    // 支持移动
    shared_ptr<Date> sp4(move(sp1));
    return 0;
}

其他使用说明

  • make_shared:相比直接 new,make_shared 会将计数器与资源分配在同一块内存中,减少内存碎片,效率更高。它不是成员函数,而是独立模板函数。

    shared_ptr<Date> sp1(new Date(2024, 9, 11));
    shared_ptr<Date> sp2 = make_shared<Date>(2024, 9, 11);
    
  • operator bool:支持隐式转换为 bool 类型,可用于判断是否为空。

    shared_ptr<Date> sp1(new Date);
    if (sp1) cout << "not nullptr" << endl;
    if (!sp1) cout << "is nullptr" << endl;
    
  • 构造函数 explicit:防止普通指针隐式转换为智能指针对象,强制显式管理所有权。

    // 报错:无法隐式转换
    // shared_ptr<Date> sp5 = new Date;
    
  • 定制删除器:默认析构使用 delete。对于 new[] 或 malloc 等资源,需指定匹配的删除器(如 delete[] 或 free)。unique_ptr 特化了数组版本,shared_ptr 可通过构造函数传入自定义删除器。

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

四、智能指针的原理

核心实现模拟

以下模拟实现了 auto_ptr、unique_ptr 和 shared_ptr 的核心功能,帮助理解底层逻辑。

namespace wusaqi {
    template<class T>
    class auto_ptr {
    public:
        auto_ptr(T* ptr) : _ptr(ptr) {}
        // 破坏性拷贝,转移所有权
        auto_ptr(auto_ptr<T>& sp) : _ptr(sp._ptr) { sp._ptr = nullptr; }
        auto_ptr<T>& operator=(auto_ptr<T>& ap) {
            if (this != &ap) {
                if (_ptr) delete _ptr;
                _ptr = ap._ptr; ap._ptr = NULL;
            }
            return *this;
        }
        ~auto_ptr() { if (_ptr) { cout << "delete:" << _ptr << endl; delete _ptr; } }
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }
    private:
        T* _ptr;
    };

    template<class T>
    class unique_ptr {
    public:
        explicit unique_ptr(T* ptr) : _ptr(ptr) {}
        ~unique_ptr() { if (_ptr) { cout << "delete:" << _ptr << endl; delete _ptr; } }
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }
        // 禁用拷贝
        unique_ptr(const unique_ptr<T>& sp) = delete;
        unique_ptr<T>& operator=(const unique_ptr<T>& sp) = 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;
    };

    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() {
            if (--(*_pcount) == 0) {
                delete _ptr; delete _pcount;
                _ptr = nullptr; _pcount = nullptr;
            }
        }
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }
        int use_count() const { return *_pcount; }
    private:
        T* _ptr;
        int* _pcount;
    };
}

shared_ptr 源码深度解析

  1. 基本结构:包含指向资源的指针 _ptr 和指向引用计数器的指针 _pcount。
  2. 引用计数:每次拷贝构造或赋值,计数器加一;析构时减一。计数归零时同时释放资源和计数器。
  3. 自赋值处理:赋值前检查是否指向同一资源,避免重复释放。
  4. 定制删除器:通过模板参数或构造函数传入可调用对象(如 lambda),替代默认的 delete 操作,确保资源释放方式正确。
namespace wusaqi {
    template<class T>
    class shared_ptr {
    public:
        shared_ptr(T* ptr = nullptr) : _ptr(ptr), _pcount(new int(1)) {}
        
        template<class D>
        shared_ptr(T* ptr, D del) : _ptr(ptr), _pcount(new int(1)), _del(del) {}

        shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del) {
            ++(*_pcount);
        }

        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;
        }

        ~shared_ptr() { release(); }
        T* get() const { return _ptr; }
        int use_count() const { return *_pcount; }
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }

    private:
        T* _ptr = nullptr;
        int* _pcount;
        function<void(T*)> _del = [](T* ptr){ delete ptr; };
    };
}

五、shared_ptr 和 weak_ptr

shared_ptr 循环引用问题

当两个 shared_ptr 互相持有对方时,引用计数永远不会归零,导致内存泄漏。例如双向链表节点互相持有对方的 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->_prev = n1;
    // 此时 n1 和 n2 的引用计数均为 2,main 结束后不会析构
    return 0;
}

weak_ptr 解决方案

weak_ptr 不管理资源所有权,不增加引用计数,专门用于绑定 shared_ptr。它将强引用改为弱引用,打破循环依赖。

struct ListNode {
    int _data;
    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; // 绑定到 weak_ptr 不增加 n2 的计数
    n2->_prev = n1;
    // 引用计数恢复正常,main 结束后可正确释放
    return 0;
}

weak_ptr 不能直接访问资源,需调用 lock() 方法尝试获取临时的 shared_ptr。若资源已销毁,lock() 返回空指针,从而避免悬垂指针风险。

std::shared_ptr<string> sp1(new string("111111"));
std::weak_ptr<string> wp = sp1;
if (!wp.expired()) {
    auto sp3 = wp.lock(); // 安全访问
    *sp3 += "###";
}

六、内存泄漏

什么是内存泄漏及危害

内存泄漏指程序未能释放不再使用的内存。短期运行的程序影响较小,但长期运行的服务(如操作系统、后台进程)若持续泄漏,会导致可用内存耗尽,系统响应变慢甚至崩溃。

如何避免内存泄漏

  1. 事前预防:优先使用智能指针管理动态资源,遵循 RAII 原则。特殊场景下自行封装资源管理类。
  2. 事后检测:定期使用内存泄漏检测工具(如 Valgrind、AddressSanitizer)扫描代码,尤其在上线前。
  3. 规范编码:确保 new 与 delete、new[] 与 delete[] 严格匹配,注意异常安全。

目录

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

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

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

更多推荐文章

查看全部
  • 单链表综合练习:删除指定值节点、反转链表及查找中间节点
  • Ollama:一条命令在本地运行 Llama2 等大模型
  • Python 移动应用开发实战:Kivy 与 BeeWare 方案详解
  • SpringBoot 微服务重试常用解决方案汇总与使用详解
  • Python 零基础学习指南
  • 三维实时渲染与 VR 全景视频的共生技术解析
  • 人工智能应用工程师(高级)课程体系解读
  • HarmonyOS6 RcButton 组件交互逻辑与事件处理机制
  • Java 长字符串处理的 5 种实用技巧
  • C++ 构造函数与初始化列表核心解析
  • FLUX.1-dev FP8 模型部署与低显存优化指南
  • Renderless 架构与 WebAgent:OpenTiny DialogBox resizable 功能实战
  • stable-diffusion-webui 照片艺术化风格迁移指南
  • Mac Intel 芯片安装 Stable Diffusion WebUI 教程
  • HarmonyOS RcTag 组件实战:技能标签墙与搜索热词
  • 用 Self-Instruct 生成微调数据,让开源模型学会写测试用例
  • 基于 Java 和 Leaflet 的湖南省道路长度 WebGIS 系统实现
  • 各行业垂直领域 AI 大模型资源汇总
  • C++ 精灵库相对运动动画演示:葫芦娃飞向太空
  • 飞书机器人接入 Seedance 2.0 国产化集成避坑指南:ARM 与国密适配

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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