智能指针
share_ptr / make_shared
ref: C++——智能指针
智能指针
why
裸指针很危险,忘记释放很容易造成内存泄漏。c++不能完全支持自动垃圾回收,但是c++11添加了智能指针实现堆内存的自动回收。
how
使用代理模式,将裸指针封装起来,构造函数里初始化,析构函数中释放。智能指针本质上是对象
shared_ptr
shared_ptr指针指向的堆内存可以同其他shared_ptr共享。
shared_ptr支持的操作:
shared_ptr<T> p; // 空智能指针,可以指向类型为 T 的对象
*p; // 解引用
p->mem; // 等价于 (*p).mem
p.get(); // 返回 p 保存的指针
p.swap(q); // 交换p和q中的指针
make_shared<T>(args); // 返回shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化对象
shared_ptr<T>p(q); // p是shared_ptr q的拷贝,q中的计数器递增
p = q; // p计数器递减,q计数器递增
p.use_count(); // p共享智能指针的数量
p.unique(); // 若p.use_count()为1,返回true,否则false
接受指针参数的智能指针的构造函数是explicit修饰的,因此,不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针
引用计数
shared_ptr 支持安全共享的原理在于内部使用了“引用计数”
如果发生拷贝赋值,引用计数就增加,而发生析构销毁的时候,引用计数就减少。当引用计数减少到 0,即没有任何人使用这个指针的时候,会调用 delete 释放内存。
注意:引用计数存在循环引用的问题。想要解决,需要使用 weak_ptr。
线程安全问题
- 引用计数的加减操作是否线程安全?
安全,因为是原子操作 - shared_ptr指向的对象是否线程安全?
不安全
当智能指针发生拷贝的时候,会先拷贝智能指针,再拷贝对象,这两个操作并不是原子的,所以不安全。
make_shared
使用shared_ptr直接创建智能指针存在内存泄露的风险。
auto px = shared_ptr<int>(new int(100));
智能指针初始化包括两个分配过程:
- new int 申请内存
- 为shared_ptr控制信息分配内存
这两个过程不是原子的,如果1成功,2失败很可能造成内存泄漏。
make_shared的做法是一次性申请一块比对象大一点的内存,多出来那块用来存放控制信息,避免了shared_ptr带来的问题。
auto p = make_shared<int>(100);
make_shared并不是完美的:
因为当强引用计数为0时,会释放引用的对象的内存,但是只有当弱引用计数为0时,才释放引用计数所占用的内存。
现在make_shared申请的内存中同时存放对象数据和引用计数,所以只有当强/弱引用都为0时,才能释放make_shared申请的一整块内存。造成对象数据占用的内存无法得到及时的释放。
释放问题
如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放。例如:
void f(){
shared_ptr<int> sp(new int(42));
// 这里代码发生了一个异常,且未被f()捕获
} // 函数结束时 shared_ptr 自动释放内存。
unique_ptr
某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
unique_ptr独有的操作:
unique_ptr<T> u1; // 空unique_ptr,u1会使用delete释放指针
unique_ptr<T,D> u2; // 空unique_ptr,u2会使用类型为D的可调用对象释放指针
unique_ptr<T,D> u(d); // 空unique_ptr,指向T类型,用类型为D的对象代替delete
u.release(); // 放弃对指针的控制权,返回指针,u置空
u.reset(q); // 释放u指向的对象,并指向q
因为unique_ptr的对象是独享的,所以不能拷贝或赋值,但可以有所有权的转移:
-
move
auto ptr1 = make_unique<int>(42); // 工厂函数创建智能指针 auto ptr2 = std::move(ptr1); // 使用move()转移所有权,ptr1变成空指针
-
release / reset
// p1 -> p2 unique_ptr<string> p2(p1.release()); // p2 -> p3 p2.reset(p3.release()); // 释放了原来指向的内存
weak_ptr
weak_ptr专门为打破循环引用而设计
因为weak_ptr只观察指针,不会增加引用计数,但在需要的时候,可以调用成员函数 lock(),获取 shared_ptr(强引用)
weak_ptr支持的操作:
weak_ptr<T> w; // 空weak_ptr
weak_ptr<T> w(sp); // 和shared_ptr指向相同的对象
w = p; // 同上
w.reset(); // 置空
w.use_count(); // 共享的shared_ptr的数量
w.expired(); // w.use_count() == 0 ? true : false;
w.lock(); // w.expired() ? nullptr(shared_ptr) : shared_ptr(point to w)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?