智能指针 学习笔记
简介
智能指针是 C++ 用于解决野指针、悬空指针、内存泄漏等问题的内存管理工具,位于头文件 <memory>
中。
野指针:未初始化的指针,非
nullptr
悬空指针:指向已释放空间的指针
其中目前还在使用的 unique_ptr
/shared_ptr
/weak_ptr
由 C++11 引入。C++14 后又有一些相关函数引入,如 std::make_unique()
等。C++20 开始支持 std::atomic<std::shared_ptr<T>>
,可以原子地操作智能指针。
智能指针的特性:
- 遵循 RAII 的原则,在构造函数中获取资源,在析构函数中释放资源
- 在初始化时会进行空指针检查,避免产生野指针
- 使用引用计数管理资源,当计数器归零时自动释放资源,避免内存泄漏或悬空指针
std::unique_ptr
- 独占式智能指针,即每个资源同时最多只能被一个
unique_ptr
拥有 - 禁止拷贝,删除了拷贝构造函数和拷贝赋值运算符,防止了多个对象同时拥有一个资源
- 支持移动语义,可以用移动语义转移资源的所有权,源指针将被置为
nullptr
std::shared_ptr
- 共享式智能指针,基于引用计数管理共享资源,常在多线程编程中使用
- 包含一个指向内存空间的指针和一个指向资源控制块的指针
shared_ptr
不直接支持数组管理,如果指向数组需要自定义删除器
std::weak_ptr
弱引用智能指针,为辅助
shared_ptr
解决循环引用问题而引入循环引用:智能指针的指向关系形成一个环,导致程序结束时资源无法释放,造成内存泄漏
弱引用:不会计入引用次数的引用,允许在必要时释放内存。通常是子结点对祖先结点的引用。不具有普通指针的功能,不具有资源的访问权,不影响引用计数,只表明一种临时的所有权
可以由
shared_ptr
构造;在需要行使指针行为时需要先尝试调用weak_ptr::lock()
创建一个shared_ptr
std::auto_ptr
auto_ptr
是早期的独占式智能指针,自 C++98 出现,在 C++17 中已被移除。
auto_ptr
的拷贝语义会转移内存的所有权,使源指针悬空,这一特性不符合人的直觉,且在与 STL 容器配合时会带来风险。同时,auto_ptr
也无法管理动态数组。因此 auto_ptr
后来被弃用并移除。
常见问题
shared_ptr 的线程安全问题
shared_ptr
资源控制块的修改(如引用计数器的增减)是原子操作,线程安全- 多线程读写同一个
shared_ptr
不是线程安全的,因为指向修改不是原子的,包含几个步骤:内容指针移动、原目标引用计数减一、控制指针移动、新目标引用计数加一 - 多线程读写共享同一资源的不同
shared_ptr
是线程安全的,因此通常按值传参而非按引用 - 多线程通过
shared_ptr
读写同一资源不是线程安全的,需要加锁
按值传递/按引用传递
unique_ptr
由于禁止了拷贝,不能按值传递,通常按引用传递,也可以使用移动语义shared_ptr
向线程传递时通常按值传递,否则容易出现线程安全问题
慎用裸指针构造/获取裸指针
- 用裸指针构造智能指针或获取智能指针的裸指针后,程序既可以从智能指针访问资源,又可以从裸指针访问资源,存在重复析构资源的风险
- 同理,不应把
this
指针作为shared_ptr
返回。作为替代,可以继承std::enable_shared_from_this
类并返回std::shared_from_this()
智能指针 学习笔记