跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ 智能指针:示例、原理与适用场景详解

原生指针易引发内存泄漏、野指针等问题。通过示例解析 shared_ptr、unique_ptr 及 weak_ptr 的原理与区别,重点剖析循环引用导致的内存泄漏机制,并提供打破循环引用的弱指针方案。结合实际 UI 组件与工厂模式案例,探讨不同智能指针在所有权管理中的适用场景,帮助开发者安全高效地管理 C++ 资源生命周期。

极客工坊发布于 2026/3/22更新于 2026/5/68 浏览
C++ 智能指针:示例、原理与适用场景详解

C++ 智能指针:示例、原理与适用场景详解

智能指针的设计初衷是为了解决原生指针带来的内存管理问题。要理解它的作用,得先看看原生指针有哪些'痛点'。

原生指针的'痛'

原生指针是 C++ 里的'双刃剑',底层灵活但也容易出错。在引入智能指针之前,裸指针常引发以下典型问题:

  • 内存泄漏:C++ 没有垃圾回收机制,全靠程序员手动 delete。一旦忘记或逻辑跳转跳过释放操作,堆内存就会泄露。
  • 野指针:包括悬空指针、未初始化指针、指向已销毁栈内存的指针等。它们指向无效地址,访问会导致未定义行为。
  • 双重释放:对同一指针重复调用 delete,属于未定义行为,大概率导致崩溃。
  • 指向超出作用域的栈内存:函数返回局部变量的地址,函数结束后栈帧弹出,指针即失效。
  • 容器里存裸指针但未清理:std::vector<int*> 存储裸指针时,容器析构不会自动删除内部元素,需手动遍历清理。
  • 指针重定向导致原对象丢失:指针重新赋值后,原对象失去引用且无法找回。
  • 异常导致释放被跳过:抛出异常时若未捕获并释放资源,会造成泄漏。

代码示例

#include <iostream>
using namespace std;

// 指向超出作用域的栈内存
int* getNum() {
    int a = 10; // a 在栈上,函数结束后销毁
    return &a;   // 返回 a 的地址
}

int main() {
    int* p = getNum();
    cout << *p << endl; // 访问野指针,结果不可控
    return 0;
}
// 双重释放
int* p = new int(10);
delete p;
delete p; // 未定义行为

智能指针的核心思想

上述问题的根源在于:指针只是一个地址值,它没有其指向对象的'所有权'。这导致了指针和对象在生命周期上不一致。

智能指针通过封装原生指针,利用控制块记录引用计数,确保在最后一个实例销毁时同步销毁目标对象。它重载了运算符,模拟指针的使用习惯,让对象像指针一样使用。

建议在实际场景中结合业务背景选择智能指针类型,不要过度纠结语义推导。从熟悉场景(如树形结构)反向体会会更清晰。

C++ 标准库提供了三种主要智能指针:shared_ptr、unique_ptr、weak_ptr。auto_ptr 已在 C++98 后被弃用。

shared_ptr:共享所有权

shared_ptr 允许多个实例指向同一对象,当最后一个实例销毁时自动释放资源。它是使用范围最广的智能指针,类似于 Java 的引用类型,但实现机制完全不同。

简单示例

int main() {
    // 注意:p1 是直接初始化的,创建在栈上
    shared_ptr<int> p1(new int(10));
    
    // 复制一份,引用计数 +1
    shared_ptr<int> p2 = p1;
    cout << "p1 use_count : " << p1.use_count() << endl; // 输出:2
    cout << "p2 use_count : " << p2.use_count() << endl; // 输出:2
    
    p2 = shared_ptr<int>(new int(6)); // p2 指向新对象,旧对象计数 -1
    cout << "p1 use_count : " << p1.use_count() << endl; // 输出:1
    cout << "p2 use_count : " << p2.use_count() << endl; // 输出:1
    return 0;
}

关键点:智能指针对象本身应创建在栈上,利用栈退出自动析构的特性省去了显式 delete。

实现原理

核心是引用计数。复制指针时计数 +1,销毁或重定向时计数 -1。计数存放在堆上的控制块中,所有指向同一对象的智能指针共享该控制块。

文章配图

图 1. shared_ptr 内部实现结构

适用场景

当多个组件都需要访问同一份数据,且谁都不是唯一所有者时(如 Cache、Config、Session),适合使用 shared_ptr。

UI 组件案例:在 UI 系统中,父组件拥有子组件,但如果布局系统、事件系统也需要持有组件指针,则必须使用 shared_ptr,否则组件可能在其他系统使用前就被销毁。

class Widget {
public:
    // 使用 shared_ptr 是因为事件、布局等其他系统也需要使用组件
    std::vector<std::shared_ptr<Widget>> children;
};

unique_ptr:独占所有权

unique_ptr 同一时刻只允许有一个实例,但支持移动语义(move),可以将所有权移交。

简单示例

#include <iostream>
#include <memory>

class File {
public:
    File(const std::string &name) { std::cout << "Open file: " << name << std::endl; }
    ~File() { std::cout << "Close file" << std::endl; }
    void write() { std::cout << "Writing..." << std::endl; }
};

void processFile(std::unique_ptr<File> file) {
    file->write();
}

int main() {
    // 创建文件对象和它的独占指针
    std::unique_ptr<File> f = std::make_unique<File>("data.txt");
    
    // unique_ptr 不允许拷贝
    // std::unique_ptr<File> f2 = f; // ❌ 编译错误
    
    processFile(std::move(f)); // 转移所有权
    
    if (!f) std::cout << "f no longer owns the file\n";
    return 0;
}

程序输出显示文件在 processFile 返回时自动关闭,因为所有权已移交。

适用场景

默认情况下优先使用 unique_ptr。如果明确知道对象属于且只属于一个对象,或者工厂模式返回对象所有权给调用者,都应首选 unique_ptr。

weak_ptr:弱引用观察

weak_ptr 不拥有对象所有权,仅观察生命周期。它通常配合 shared_ptr 使用,解决循环引用问题。

简单示例

int main() {
    auto p1 = make_shared<int>(10);
    weak_ptr<int> p2(p1); // 创建弱引用
    
    cout << "p1 use_count is: " << p1.use_count() << endl; // 输出:1
    // weak_ptr 不影响 shared_ptr 的引用计数
    return 0;
}

正确用法:使用 lock() 方法获取有效的 shared_ptr 后再操作,避免对象意外销毁。

if (auto ptr = w_ptr.lock()) {
    // lock() 返回 shared_ptr,为空则表示目标对象已销毁
    // do something...
} else {
    cout << "A 对象已销毁" << endl;
}

循环依赖问题

weak_ptr 的主要用途是解除 shared_ptr 的循环引用导致的内存泄漏。

问题分析

如果 A 持有 B 的 shared_ptr,B 又持有 A 的 shared_ptr,双方引用计数永远无法归零,导致内存泄漏。

class B; class A;

class A {
public:
    int v;
    shared_ptr<B> b;
    A(int val) : v(val) { cout << "A 构造函数" << endl; }
    ~A() { cout << "A 析构函数" << endl; }
};

class B {
public:
    int v;
    shared_ptr<A> a;
    B(int val) : v(val) { cout << "B 构造函数" << endl; }
    ~B() { cout << "B 析构函数" << endl; }
};

int main() {
    shared_ptr<A> ptrA = make_shared<A>(10);
    shared_ptr<B> ptrB = make_shared<B>(20);
    
    ptrA->b = ptrB; // A 持有 B
    ptrB->a = ptrA; // B 持有 A
    
    return 0; // 离开 main 后,ptrA, ptrB 销毁,但 A, B 对象因互相持有而无法释放
}

打破循环

将其中一方的强引用改为弱引用即可。

class B {
public:
    int v;
    weak_ptr<A> a; // 关键修改:使用 weak_ptr
    B(int val) : v(val) { cout << "B 构造函数" << endl; }
    ~B() { cout << "B 析构函数" << endl; }
};

int main() {
    shared_ptr<A> ptrA = make_shared<A>(10);
    shared_ptr<B> ptrB = make_shared<B>(20);
    
    ptrA->b = ptrB; // A 仍持有 B 的强引用
    ptrB->a = ptrA; // B 持有 A 的弱引用
    
    return 0; // 正常释放,无泄漏
}

传值还是引用?

讨论智能指针的参数传递方式:

  1. shared_ptr + 值传递:默认方式,会复制智能指针实例,增加引用计数。适用于需要共享所有权的场景。
  2. shared_ptr + 引用传递:表达'借用'姿态,不改变生命周期。需注意多线程环境下对象可能被别处释放的风险。
  3. unique_ptr + 引用传递:由于 unique_ptr 不可复制,这是唯一在别处使用的方式,语义为只借用对象,不拥有。

在实际开发中,如果没有特殊需求,默认使用 unique_ptr,遇到明确需要共享所有权时再改用 shared_ptr。

目录

  1. C++ 智能指针:示例、原理与适用场景详解
  2. 原生指针的“痛”
  3. 代码示例
  4. 智能指针的核心思想
  5. shared_ptr:共享所有权
  6. 简单示例
  7. 实现原理
  8. 适用场景
  9. unique_ptr:独占所有权
  10. 简单示例
  11. 适用场景
  12. weak_ptr:弱引用观察
  13. 简单示例
  14. 循环依赖问题
  15. 问题分析
  16. 打破循环
  17. 传值还是引用?
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 多模态融合:RetinaFace、CurricularFace 与语音识别系统
  • 使用 OpenClaw 与飞书搭建专属 AI 机器人
  • OpenClaw 移动端部署实战:iOS/Android 语音唤醒与离线 AI 助手
  • C++ 模板的幻觉:实例化、重定义与隐藏依赖
  • Git-AI:追踪 AI 生成代码的实用工具
  • Python 库 unstructured:高效转换 PDF、Word 等非结构化数据
  • GPT-4o 多模态大模型详解与性能分析
  • 基于 SpringBoot 和 Leaflet 的省级行政区及简称可视化
  • 基于 LLaMA-Factory 微调 Qwen3-VL 多模态模型实战
  • 什么是生成式人工智能?
  • Stable Diffusion WebUI 部署与使用指南
  • OpenDroneMap 无人机影像三维重建:安装与实战指南
  • LLaMA-Factory 详细安装教程
  • C++ 泛型编程与模板机制详解
  • 2025 年智能机器人操作系统(AGIROS)开源社区生态大会
  • BeagleBone Black 从 SD 卡启动 Android 系统及性能评测
  • 基于 GitLab CI/CD 与 DeepSeek 的 AI Code Review 自动化方案
  • 三维实时渲染与 VR 全景视频的共生模式与技术抉择
  • DALL·E 3 绘图功能与 API 使用指南
  • 基于龙卷风优化算法的多无人机协同路径规划及 Matlab 实现

相关免费在线工具

  • 加密/解密文本

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