c++智能指针:std::unique_ptr , std::shared_ptr , std::weak_ptr

目录

为什么需要智能指针?

什么是智能指针?

智能指针的本质

 三种智能指针(来自 头文件)

1. std::unique_ptr

2. std::shared_ptr

3. std::weak_ptr

引用计数(reference counting)

什么是循环引用?为什么需要 weak_ptr?

什么时候用哪种智能指针?

第一性角度再总结一遍:


为什么需要智能指针?

我们从一个根本问题开始:

C++ 中我们用 new 创建对象,释放时要用 delete,你自己负责内存管理。但如果你忘了 delete 呢?

void func() {
    int* p = new int(10);
    // 忘了 delete p;
}

这段代码执行完后,堆上的内存没有被释放,内存泄漏了!

  • 每次 new 都要记得 delete

  • 多个函数共享一个指针,delete 一次还好,多了就崩溃

  • 异常发生时,如果没进到 delete,也会泄漏

于是,我们可以问一个第一性问题: 

有没有一种机制,当对象不再需要时,自动帮我释放内存? 

C++ 说:有!那就是——智能指针(smart pointers)。

什么是智能指针?

通俗来说:智能指针就是**“带有脑子的指针”,它不仅能像普通指针一样指向对象,还能在合适的时候自动释放对象的内存**。

它封装了一个裸指针,但同时还管理这个指针的生命周期,防止你忘记释放资源。

 

智能指针的本质

template
class SmartPointer {
private:
    T* ptr;
public:
    SmartPointer(T* p) : ptr(p) {}
    ~SmartPointer() { delete ptr; }
    T* operator->() { return ptr; }
    T& operator*() { return *ptr; }
};

这个简化版就能说明问题了:

  • 构造时接管一个裸指针

  • 析构时自动 delete

  • 重载 *->,让它像普通指针一样使用

这就是智能指针的基本思想!


 

 三种智能指针(来自 头文件)

1. std::unique_ptr

独占型智能指针,一个对象只能有一个主人。 

#include 

std::unique_ptr p = std::make_unique(42);
std::cout << *p << std::endl;


//转移所有权:
std::unique_ptr q = std::move(p);  // p 被“清空”,资源被 q 拥有

特点:

  • 只能有一个指针拥有这块资源

  • 不可复制(copy),但可以移动(move)

  • 离开作用域后自动 delete

 底层实现unique_ptr 持有一个裸指针,且该指针在 unique_ptr 析构时会被释放。unique_ptr

支持移动语义(std::move),允许所有权转移,但禁止拷贝。

[unique_ptr] --> [资源]

2. std::shared_ptr

共享型智能指针,多个指针可以共享一个对象。 

#include 

std::shared_ptr p1 = std::make_shared(42);
std::shared_ptr p2 = p1;  // 引用计数 +1

特点:

  • 多个 shared_ptr 可以共同管理一块内存

  • 使用引用计数(reference counting)

  • 最后一个 shared_ptr 被销毁时,资源才被释放

#include 

auto p1 = std::make_shared(100);
auto p2 = p1;  // 引用计数 +1

 你可以用 .use_count() 查看当前有几个指针共享:

std::cout << p1.use_count();  // 输出 2

底层实现shared_ptr 内部会维护一个引用计数,当一个 shared_ptr 被复制时,它会增加引用计

数;当一个 shared_ptr 被销毁时,它会减少引用计数。只有当引用计数为 0 时,资源才会被释

放。

[shared_ptr1] -->┐
[shared_ptr2] -->│--> [资源] + [引用计数]
[shared_ptr3] -->┘

 

3. std::weak_ptr

 弱引用,不参与引用计数,防止循环引用 

#include 

std::shared_ptr a = std::make_shared();
std::weak_ptr wa = a;  // wa 不增加引用计数

特点:

  • 不增加引用计数

  • 不会影响资源释放

  • 可用于解决 shared_ptr 循环引用的问题

  • 不能直接使用,要用 .lock() 转成 shared_ptr

std::shared_ptr sp = std::make_shared(123);
std::weak_ptr wp = sp;

if (auto spt = wp.lock()) {
    std::cout << *spt << std::endl;
}

底层实现weak_ptr 内部持有一个指向 shared_ptr 的指针,但不增加引用计数。当你想访问

weak_ptr 所管理的对象时,需要通过 lock() 方法将它转换为一个有效的 shared_ptr,如果资源已

被释放,lock() 返回一个空的 shared_ptr

[weak_ptr] -X-> [资源]

引用计数(reference counting)

什么是引用计数? 

引用计数是一种自动管理资源生命周期的机制。 

它的核心思想是:

有多少人正在用这块资源?我就记录一个‘计数器’。当没人用了,我就把它释放掉。

这个“多少人”指的就是指向资源的智能指针(通常是 shared_ptr)。

 为什么需要引用计数?

比如你用 shared_ptr 管理一个对象: 

auto p1 = std::make_shared(10);
auto p2 = p1;

 这个资源(内存里的 10)现在有两个指针在用它(p1 和 p2)。

引用计数的目标是:

  • 每次有一个新指针指向资源,计数 +1

  • 每次有一个指针销毁或离开作用域,计数 -1

  • 当计数 = 0,说明没人用了,自动 delete 资源 

  怎么实现引用计数(背后的机制)?

通常情况下,一个 shared_ptr 实际上会维护一个 控制块(control block),里面包括: 

┌─────────────────────────────┐
│     控制块 Control Block     │
├─────────────────────────────┤
│ 资源指针 ptr → new T(...)    │
│ 引用计数 use_count           │
│ 弱引用计数 weak_count        │
└─────────────────────────────┘
  • use_count:当前有几个 shared_ptr 正在使用资源

  • weak_count:有几个 weak_ptr 正在“观察”资源

#include 
#include 

int main() {
    std::shared_ptr p1 = std::make_shared(42);
    std::shared_ptr p2 = p1;  // use_count = 2
    std::shared_ptr p3 = p2;  // use_count = 3

    std::cout << p1.use_count() << std::endl;  // 输出 3

    p2.reset();  // use_count = 2
    std::cout << p1.use_count() << std::endl;

    p3.reset();  // use_count = 1
    std::cout << p1.use_count() << std::endl;

    p1.reset();  // use_count = 0,资源自动释放 ✅
}
行为 引用计数变化
拷贝 shared_ptr +1
reset() 或销毁 -1
计数为 0 自动释放资源

 


什么是循环引用?为什么需要 weak_ptr

循环引用是指:当两个对象的 shared_ptr 相互指向对方时,它们的引用计数不会归零,因为它们各自持有对方的 shared_ptr。这导致对象永远不能被销毁,从而产生内存泄漏。

解决办法是,使用 weak_ptr 代替其中一个 shared_ptr,这样它就不会增加引用计数,防止了循环引用。

struct Node {
    std::shared_ptr next;
};

std::shared_ptr a = std::make_shared();
std::shared_ptr b = std::make_shared();

a->next = b;
b->next = a;  // ❌ 循环引用,永远不会释放!

正确写法:

struct Node {
    std::weak_ptr next;
};

这样 b 指向 a 的引用就不会增加计数了,内存可以正常释放。 


 

什么时候用哪种智能指针?

类型 适合场景
unique_ptr 默认首选,局部资源管理,函数返回,单一所有者
shared_ptr 多个模块共享资源(如树状结构、观察者模式)
weak_ptr 避免 shared_ptr 的循环引用

第一性角度再总结一遍:

智能指针的本质是:

RAII:Resource Acquisition Is Initialization
——资源获取即初始化,对象一创建就接管资源,一销毁就自动释放。

它通过:

  • 构造函数负责获取资源

  • 析构函数负责释放资源

实现了对象生命周期 = 资源生命周期

你可能感兴趣的:(C/C++,c++,开发语言,c语言)