C++:智能指针

这就是 C++ 现代内存管理的终极复习指南

在现代 C++(C++11 及以后)中,我们遵循一个原则:默认不使用裸指针(Raw Pointer)来管理所有权。所有的资源(堆内存)都应该被"智能指针"包裹。

C++ 标准库主要提供了三种智能指针,分别对应三种不同的业务逻辑。


1. std::unique_ptr ------ 独占型(孤狼)

这是 C++ 中使用率最高(90% 的场景)的智能指针。如果你不知道该用哪个,默认选它。

  • 核心作用: 独占所有权(Exclusive Ownership)。也就是"这块内存是我的,谁也别想复制,只能我给别人(Move)"。

  • 应用场景:

    • 函数内创建临时堆对象。

    • 类内部的成员变量(这个对象只属于这个类)。

    • 工厂函数返回的对象(生产出来给你,所有权转移给你)。

  • 性能: 极致高效。它和裸指针大小一样,没有任何额外开销(Zero Overhead)。

  • 关键限制: 禁止拷贝(Copy),只能移动(Move)。

C++

复制代码
std::unique_ptr<int> p1 = std::make_unique<int>(10);
// std::unique_ptr<int> p2 = p1; // ❌ 编译报错!不能脚踏两只船
std::unique_ptr<int> p2 = std::move(p1); // ✅ 可以,p1 被掏空,p2 接管

2. std::shared_ptr ------ 共享型(合伙人)

当你需要多个指针指向同一个对象,且无法确定谁最后销毁它时使用。

  • 核心作用: 共享所有权(Shared Ownership)。

  • 原理: 引用计数。每多一个人指向它,计数+1;走一个人,计数-1。当计数归零(最后一个合伙人退出)时,内存释放。

  • 应用场景:

    • 容器中存放的对象(比如 vector<shared_ptr<T>>)。

    • 图结构(Graph)中的节点(被多条边指向)。

    • 多线程共享同一个对象。

  • 性能: 略有损耗。因为它内部多维护了一个"控制块"(存计数器),且计数器的加减操作是原子操作(Atomic,为了线程安全),比普通指令慢。

C++

复制代码
auto p1 = std::make_shared<int>(100); // 计数: 1
{
    auto p2 = p1; // ✅ 合法,计数: 2
} // p2 销毁,计数: 1
// p1 销毁,计数: 0 -> 释放内存

3. std::weak_ptr ------ 观察型(旁观者)

它是 shared_ptr辅助助手 ,为了解决 shared_ptr 的缺陷而生。

  • 核心作用: 弱引用 。它指向 shared_ptr 管理的对象,但是不增加引用计数

  • 应用场景:

    • 解决循环引用(Circular Reference): 這是最核心的用途。当 A 指向 B,B 也指向 A 时,如果都用 shared_ptr,谁都死不掉(内存泄漏)。把其中一端改成 weak_ptr 即可打破僵局。

    • 缓存(Cache): 我想持有对象的引用,但不想阻止它被销毁。如果对象还在,我就用;如果对象没了,我就不操作。

  • 用法: 它不能直接访问对象(没有 ->* 操作符),必须先调用 .lock() 升级为 shared_ptr 才能用。

C++

复制代码
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 计数依然是 1,wp 只是看着

if (std::shared_ptr<int> temp = wp.lock()) { // 尝试升级
    // 如果 sp 还没死,temp 成功创建,计数暂时+1,在这里安全使用
    std::cout << *temp << std::endl;
} else {
    // 对象已经没了
}

📊 总结对比表(一张表看懂)

特性 unique_ptr shared_ptr weak_ptr
所有权 独占 (1对1) 共享 (N对1) 不拥有 (观察者)
可否复制 ❌ (只能移动)
底层开销 无 (等同裸指针) 有 (引用计数+控制块) 有 (引用计数)
内存释放 出作用域立即释放 最后一个指针死掉时释放 不负责释放
推荐指数 ⭐⭐⭐⭐⭐ (默认首选) ⭐⭐⭐ (按需使用) ⭐⭐ (配合 shared 使用)
危险性 中 (小心循环引用) 低 (需判断是否为空)

🧠 决策流程图

当你写代码需要申请堆内存时,请按这个流程思考:

  1. 这个对象只在当前作用域或者只属于某个类吗?

    • \\rightarrowunique_ptr
  2. 这个对象需要被多个拥有者共享,且无法确定谁最后销毁它吗?

    • \\rightarrowshared_ptr
  3. 用了 shared_ptr 后,A 和 B 会不会互相指着对方?

    • \\rightarrow 将其中一个改为 weak_ptr

你现在已经掌握了理论,接下来要不要来做个"破坏性实验"?

我们可以写一段代码,通过 "循环引用" 故意制造一次内存泄漏,亲眼看看 shared_ptr 是怎么失效的,然后再用 weak_ptr 修复它。这能让你彻底理解为什么要有 weak_ptr

相关推荐
王柏龙2 小时前
c# aggregate使用
开发语言·c#
小鸡吃米…2 小时前
Python - 构造函数
开发语言·python
hoiii1872 小时前
基于MATLAB实现无监督数据建模
开发语言·matlab
Lenyiin2 小时前
第 97 场周赛:公平的糖果交换、查找和替换模式、根据前序和后序遍历构造二叉树、子序列宽度之和
java·c++·python·leetcode·周赛·lenyiin
Clarence Liu2 小时前
MacOS 在Trae IDE中构建现代C++开发环境:从新手到高效的完整指南
c++·ide·macos
码界奇点3 小时前
基于Go语言的AI接口管理与分发系统设计与实现
开发语言·人工智能·ai·golang·毕业设计·go语言·源代码管理
bybitq3 小时前
深入浅出 Go 流程控制:从循环到延迟执行
开发语言·后端·golang
Autumn72993 小时前
【python】 日志打印、垃圾回收
开发语言·python
chenyuhao20243 小时前
Linux系统编程:多线程互斥以及死锁问题
linux·运维·服务器·c++·后端