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

C++ 内存管理进阶:从裸指针到智能指针

C++ 内存管理进阶:从裸指针到智能指针。详细解析了 unique_ptr、shared_ptr 和 weak_ptr 的核心原理与使用场景,涵盖引用计数机制、循环引用解决方案及定制删除器等进阶技巧。通过对比裸指针风险与智能指针优势,提供最佳实践建议,帮助开发者构建更安全的 C++ 代码。

漫步发布于 2026/3/28更新于 2026/10/2590 浏览
C++ 内存管理进阶:从裸指针到智能指针

前言

在 C++ 开发中,内存管理始终是核心挑战之一。指针赋予了直接操作内存的能力,但也带来了内存泄漏、野指针、重复释放等风险。C++11 引入的智能指针通过 RAII(资源获取即初始化)机制,将资源生命周期与对象生命周期绑定,实现了内存的自动回收。

本文将深入解析 unique_ptr、shared_ptr 和 weak_ptr 的设计原理与实战用法,帮助你在保证安全的前提下提升代码质量。

一、裸指针的风险

在使用智能指针之前,有必要回顾一下裸指针(Raw Pointer)常见的陷阱。

1.1 内存泄漏

当分配的堆内存未被正确释放时,就会发生内存泄漏。这在复杂逻辑或异常路径中尤为常见。

void func() {
    int* p = new int(10);
    if (some_condition) {
        return; // 提前返回,p 未释放
    }
    delete p;
}

若 some_condition 为真,函数提前返回,delete p 永远不会执行,导致内存泄漏。

1.2 二次释放

对同一块内存多次调用 delete 会破坏堆结构,引发未定义行为。

void func() {
    int* p = new int(20);
    delete p;
    // ... 逻辑处理 ...
    delete p; // 错误:重复释放
}

1.3 野指针

指向已释放或非法地址的指针称为野指针。访问它可能导致程序崩溃。

int* func() {
    int x = 10;
    return &x; // 返回栈变量地址,函数结束后成为野指针
}

1.4 异常安全

异常抛出会中断正常流程,若未妥善处理资源释放,会导致泄漏。

void func() {
    int* p = new int(30);
    try {
        throw std::runtime_error("error");
    } catch (...) {
        // 此处未释放 p
        throw;
    }
    delete p; // 不会执行
}

1.5 智能指针的使命

智能指针的核心思想是封装裸指针,并在析构时自动释放资源。利用 C++ 对象'出作用域即析构'的特性,确保资源被正确回收。

二、智能指针详解

C++11 提供了三种主要智能指针:unique_ptr、shared_ptr 和 weak_ptr。

2.1 unique_ptr:独占所有权

unique_ptr 是最轻量级的智能指针,拥有独占所有权,不可拷贝,仅支持移动语义。

原理

底层封装裸指针,禁用拷贝构造函数和赋值运算符,支持移动构造和赋值。析构时自动调用 delete。

使用示例
#include <memory>
#include <iostream>
using namespace std;

class Test {
public:
    Test(int id) : id_(id) { cout << "Test(" << id_ << ") 构造" << endl; }
    ~Test() { cout << "Test(" << id_ << ") 析构" << endl; }
    void show() { cout << "Test id: " << id_ << endl; }
private:
    int id_;
};

void test_unique_ptr_basic() {
    // 推荐方式:make_unique
    unique_ptr<Test> up1 = make_unique<Test>(1);
    up1->show();

    // 移动语义转移所有权
    unique_ptr<Test> up2 = move(up1);
    up2->show();
    // up1 此时变为 nullptr
}

最佳实践:

  • 优先使用 make_unique 创建,避免 new 表达式可能带来的异常安全问题。
  • 管理数组时使用 unique_ptr<T[]>,确保调用 delete[]。

2.2 shared_ptr:共享所有权

当多个对象需要共享同一资源时,shared_ptr 通过引用计数机制实现共享所有权。

原理

每个 shared_ptr 指向一个控制块,其中包含引用计数。当最后一个 shared_ptr 销毁时,引用计数归零,资源被释放。

文章配图

使用示例
#include <memory>
#include <iostream>
using namespace std;

void test_shared_ptr_basic() {
    shared_ptr<Test> sp1 = make_shared<Test>(1);
    shared_ptr<Test> sp2 = sp1; // 引用计数 +1
    cout << "use_count: " << sp1.use_count() << endl; // 输出 2
}
循环引用问题

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

shared_ptr<Test> sp1 = make_shared<Test>(100);
shared_ptr<Test> sp2 = make_shared<Test>(200);
sp1->sp_self = sp2; // 假设成员变量为 shared_ptr
sp2->sp_self = sp1;
// 离开作用域后,引用计数均为 1,无法释放

2.3 weak_ptr:打破循环

weak_ptr 是一种弱引用,不增加引用计数,用于观察 shared_ptr 管理的对象。

原理

weak_ptr 指向控制块但不管理对象。通过 lock() 方法可获取临时的 shared_ptr 访问对象。

解决循环引用

将循环引用中的一方改为 weak_ptr。

class Test {
public:
    weak_ptr<Test> wp_self; // 使用 weak_ptr 代替 shared_ptr
    // ... 其他成员
};

void solve_cycle() {
    shared_ptr<Test> sp1 = make_shared<Test>();
    shared_ptr<Test> sp2 = make_shared<Test>();
    sp1->wp_self = sp2; // 不增加 sp2 的引用计数
    sp2->wp_self = sp1;
    // 离开作用域后,引用计数归零,正常释放
}

三、进阶技巧

3.1 定制删除器

默认删除器使用 delete,特殊资源(如文件句柄)需自定义。

auto close_file = [](FILE* fp) { if(fp) fclose(fp); };
unique_ptr<FILE, decltype(close_file)> fp(fopen("test.txt", "w"), close_file);

3.2 类型转换

智能指针不支持直接 cast,需使用专用函数。

  • static_pointer_cast:静态转换
  • dynamic_pointer_cast:动态转换(需多态)
  • const_pointer_cast:去除 const
shared_ptr<Base> base = make_shared<Derived>();
auto derived = dynamic_pointer_cast<Derived>(base);

3.3 性能优化

  1. 优先 unique_ptr:无额外开销。
  2. 使用 make_shared:一次性分配对象和控制块,减少内存碎片。
  3. 避免拷贝:传递 shared_ptr 时尽量用 const&。
  4. 及时释放 lock:weak_ptr::lock() 返回的 shared_ptr 应尽快离开作用域。

四、避坑指南

  1. 禁止混合管理:不要将裸指针同时交给多个智能指针管理。
  2. 检查有效性:使用 weak_ptr 前检查 expired() 或判断 lock() 返回值。
  3. 数组类型:unique_ptr<int[]> 而非 unique_ptr<int>。
  4. 慎用 get/release:暴露裸指针可能破坏所有权规则。

总结

智能指针通过 RAII 机制大幅降低了内存管理的复杂度。在实际开发中,遵循'能不用裸指针就不用'的原则,根据所有权需求选择合适的智能指针,能让代码更安全、更易维护。

目录

  1. 前言
  2. 一、裸指针的风险
  3. 1.1 内存泄漏
  4. 1.2 二次释放
  5. 1.3 野指针
  6. 1.4 异常安全
  7. 1.5 智能指针的使命
  8. 二、智能指针详解
  9. 2.1 unique_ptr:独占所有权
  10. 原理
  11. 使用示例
  12. 2.2 shared_ptr:共享所有权
  13. 原理
  14. 使用示例
  15. 循环引用问题
  16. 2.3 weak_ptr:打破循环
  17. 原理
  18. 解决循环引用
  19. 三、进阶技巧
  20. 3.1 定制删除器
  21. 3.2 类型转换
  22. 3.3 性能优化
  23. 四、避坑指南
  24. 总结

更多推荐文章

查看全部
  • 常见排序算法详解与 Java 实现
  • AI 大模型全面概述
  • VSCode Copilot 配置文件提示未知工具警告
  • 递归与回溯算法实战:汉诺塔、链表操作及快速幂
  • AI 产品经理入门指南:核心技能与常见误区解析
  • 字节跳动音视频前端一面面经与深度解析
  • Git 基本使用指南
  • Visual C++ 运行库检测工具原型开发 (Python+PyQt)
  • AI 编程工具深度对比:Cursor、Copilot、Trae 与 Claude Code
  • 基于 Unity 和 AI 零代码制作小游戏“飞翔的牛马”
  • 数据结构:双向链表实现与算法分析
  • 大模型产品经理转型指南:从零开始的全面路径
  • AI 时代打造超级个体:个人效能提升与实战路径
  • JavaScript 对象与数组核心用法
  • 阿里云 DashScope、OpenAI 及 aiohttp Python SDK 异步性能对比
  • Vue3 + TypeScript Promise 转 string 类型错误解析及异步处理最佳实践
  • 机器人底盘无刷电机 PWM 调速板驱动 75mm 麦克纳姆轮
  • AI+AR智能眼镜赛道年终观察
  • LeetCode 92 链表区间反转:递归反转与哨兵技巧
  • C++ 二叉搜索树:从基础概念到代码实现

相关免费在线工具

  • 加密/解密文本

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