c++ 智能指针unique_ptr(c++ 智能指针总结)
内存管理
堆
定义:动态分配内存的区域
c++会设计到的两个有关内存管理器的操作
让内存管理器分配一个某大小的内存块
分配内存要考虑程序当前已经有多少未分配的内存
内存不足时要从操作系统申请新的内存;内存充足时 ,从可用内存里取出一块合适大小的内存 ,将其标记为已用 ,再将其返回给要求内存的代码
让内存管理器释放一个之前分配过的内存块
对于连续未使用的内存块 ,内存管理器需要将其合并成一块 ,以便可以满足后续较大内存的分配
栈
定义:函数调用过程中产生的本地变量和调用数据的区域 大致执行过程: 当函数调用另一个函数时 ,会把参数也压入栈里 ,然后把下一行汇编指令的地址压入栈 ,并跳转到新的函数 。新的函数进入后 ,首先做一些必须的保存工作 ,然后会调整栈指针 ,分配出本地变量所需的空间 ,随后执行函数中的代码,并在执行完毕之后 ,根据调用者压入栈的地址 ,返回到调用者未执行的代码中继续执行 优点: 栈上的分配十分简单,只需移动栈指针 栈上的释放十分简单 ,函数执行结束时只需移动栈指针 由于后进先出的执行过程 ,不可能出现内存碎片RAII(Resource Acquisition Is Initialization)
定义:RAII 依赖于栈和析构函数 ,对所有的资源进行管理 栈方便且风险低 ,为什么使用RAII? 对象占用内存很大 在编译期不能确定对象的大小 对象是函数的返回值 ,但由于特殊原因(如对象切片) ,不应使用对象的值进行返回 用途: 在析构函数里释放内存 关闭文件 释放同步锁 释放其他重要的系统资源智能指针
什么是智能指针? 完全实践RAII ,包装裸指针 行为类似常规指针 ,但负责自动释放所指对象 一个模板 使用动态内存 为什么使用智能指针? 能够自动适应各种复杂的情况 ,防止误用指针导致的隐患 。如 ,内存泄漏 三种智能指针 。都定义在<memory> unique_ptr 。独占所指对象 shared_ptr 。允许多个指针指向同一对象 weak_ptr 。一种弱引用 ,指向shared_ptr所管理的对象 建议 内置指针仅用于范围 、循环或助手函数有限的小代码块中 ,这些代码块的性能非常关键,而且不存在混淆所有权的可能性 在单独的代码行上创建智能指针 ,而不要在参数列表中创建智能指针 ,这样就不会因为参数列表分配规则而发生可能的资源泄漏unique_ptr
操作
unique_ptr
unique_ptr
u1自己调用delete释放它的指针;u2自己调用一个类型为D的可调用对象来释放它的指针
unique_ptr
其中 ,T是对象类型 ,D为删除器
更多的参见shared_ptr特性:
一个unique_ptr只能指向一个对象 ,且当unique_ptr被销毁时 ,它所指向的对象也被销毁 unique_ptr离开作⽤域时 ,若其指向对象 ,则将其所指对象销毁(默认delete) unique_ptr不是指针 ,而是对象 定义unique_ptr时,需要将其绑定到一个new返回的指针 不能对unique_ptr调用delete 。它会自动管理初始化时的指针 ,在离开作用域时析构释放内存 不支持加减运算 ,不能随意移动指针地址 不支持普通的拷贝或赋值所有权
一个unique_ptr只能指向一个对象,且当unique_ptr被销毁时 ,它所指向的对象也被销毁 。为了实现这个目的 ,unique_ptr使用了move语义 ,且禁止拷贝赋值 ,必须使用std::move()显示地声明所有权转移 尽量不要对 unique_ptr 执行赋值操作shared_ptr
操作
shared_ptr和unique_ptr都支持的操作
shared_ptr
unique_ptr
p.swap(q)
交换p和q中的指针
shared_ptr独有的操作
make_shared
使用args初始化此对象
shared_ptr
递增q的引用计数
q中的指针必须可以转换为T* p = q p 和 q都是shared_ptr ,所保存的指针必须可以相互转换
递减p的引用计数 ,递增q的引用计数 p.use_count() 返回与p共享对象的智能指针数量
比较慢 ,用于调试
p.unique()
若p.use_count() 为1 ,返回true ,否则false
定义和改变shared_ptr
shared_ptr
将u置空
shared_ptr
p使用可调用对象d代替delete
shared_ptr
p.reset(q)
p.reset(q,d) 若p是唯一指向对象的shared_ptr,reset会释放p的对象
若传递内置指针q ,则令p指向q,否则将p置空
若还传递d ,使用可调用对象d代替delete所有权
与unique_ptr的所有权不同 ,shared_ptr的所有权可以被安全共享 。因为它的内部使用引用计数特性
当引用计数减少到 0 ,shared_ptr会自动调用 delete 释放内存 shared_ptr的实现机制是在拷⻉构造时使⽤同⼀份引⽤计数 当对象不再被使用 ,shared_ptr会自动调用 delete 释放内存 shared_ptr可以在任何场合替代内置指针 ,而不用担心资源回收的问题注意事项
引用计数的存储和管理都是成本 ,不如unique_ptr
引用计数的变动非常复杂 ,很难知道其真正释放资源的时机 。因此 ,对象的析构函数不要有非常复杂 、严重阻塞的操作
无法解决循环引用问题
不建议在函数的参数使用shared_ptr ,因为成本高
同⼀个shared_ptr被多个线程“读 ”是安全的
同⼀个shared_ptr被多个线程“写 ”是不安全的
在多个线程中同时对⼀个shared_ptr循环执⾏两遍swap。 shared_ptr的swap函数的作⽤就是和另外⼀个shared_ptr交换引⽤对象和引⽤计数 ,是写操作 。执⾏两遍swap之后, shared_ptr引⽤的对象的值应该不变
共享引⽤计数的不同的shared_ptr被多个线程 ”写“ 是安全的
weak_ptr
操作
weak_ptr
weak_ptr
T必须能转换为sp指向的类型 w = p p可以是shared_ptr/weak_ptr
赋值后w 和 p共享对象 w.reset() 将w置空 w.use_count() 与w共享对象的shared_ptr的数量 w.expired() 若w.use_count 为0,返回true,否则false w.lock() 若expired为true ,返回一个空的shared_ptr
否则返回一个指向w的对象的shared_ptr什么是wake_ptr?
wake_ptr是一种不控制所指对象生存期的智能指针 ,指向一个由shared_ptr管理的对象为什么使用wake_ptr?
解决shared_ptr存在的循环引用问题
循环引用:
class Node final { public: using this_type= Node; using shared_type = std::shared_ptr<this_type>; public: shared_type next; // 使用智能指针来指向下一个节点 }; auto n1 = make_shared<Node>(); // 工厂函数创建智能指针 auto n2 = make_shared<Node>(); // 工厂函数创建智能指针 assert(n1.use_count() == 1);// 引用计数为1 assert(n2.use_count() == 1); n1->next = n2; // 两个节点互指,形成了循环引用 n2->next = n1; assert(n1.use_count() == 2); // 引用计数为2 assert(n2.use_count() == 2); // 无法减到0 ,无法销毁 ,导致内存泄漏解决:
将shared_ptr改成weak_ptr
class Node final { public: using this_type= Node; using shared_type = std::weak_ptr<this_type>; public: shared_type next; // 使用智能指针来指向下一个节点 }; //... if (!n1->next.expired()) // 检查指针是否有效 { auto ptr = n1->next.lock(); // lock()获取shared_ptr assert(ptr == n2); }注意事项
将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数 一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放 ,即使weak_ptr指向对象 创建一个weak_ptr时 ,需要用shared_ptr初始化 由于对象可能不存在 ,不能使用weak_ptr直接访问对象 ,而是调用lock()reference
Smart pointers (Modern C++) | Microsoft Learn
罗剑锋的 C++ 实战笔记 (geekbang.org)
C++ Primer 中文版(第 5 版) (豆瓣) (douban.com)
知识星球 | 深度连接铁杆粉丝 ,运营高品质社群 ,知识变现的工具 (zsxq.com)
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!