智能指针摘要
智能指针
引入背景
- 悬空指针被使用:有些内存资源已经被释放,但指向它的指针并没有改变指向,并且后续还在使用
- 二次释放:有些内存资源已经被释放,后期又试图再释放一次(重复释放同一块内存会导致程序运行崩溃)
- 堆内存泄露(忘记释放):没有及时释放不再使用的内存资源造成内存泄漏,程序占用的内存资源越来越多;程序发生异常时内存泄露。
unique_ptr
- 内存布局(
sizeof
大小为8字节)
基本开销成本同裸指针一致,在未自定义删除器操作时
- 函数对象、lambda(注意捕获效应会导致
lambda
对象变大)、函数指针、函数适配器std::bind
、可调用对象std::function
- 函数对象、lambda(注意捕获效应会导致
函数声明
template<class T, class Deleter = std::default_delete> class unique_ptr;
template<class T, class Deleter = std::default_delete> class unique_ptr<T[], Deleter>;
- 目的是采用函数重载方式,支持对数组类对象的相应管理
特点
- 通过
RAII
机制,实现异常保证 - 拥有动态生命周期的唯一对象所有权
- 禁用拷贝,可用移动传递所有权
- 通过
相关api
release
暴露相应的裸指针,并且释放所有权reset
重置所需管理的所有权- 无参数,仅仅是置空
- 有参数,置空后指向相应的指针
swap
交换管理的所有权get
返回对象的裸指针get_deleter
返回对象的析构器make_unique
构造一个智能指针对象
推荐采用
make_unique
- 避免因为异常导致的内存泄漏
常见陷阱
- 使用
unique_ptr
获取同一个指针的所有权,会导致双重删除 unique_ptr(this)
会抢夺this所有权,不可取。release
接口并没有负责删除get
之后,仅是暴露了裸指针,不可转移相应的所有权unique_ptr
用在多态基类时,父类必须实现虚析构
- 使用
使用场景
- 为动态分配实现异常安全
- 从函数内返回动态分配的内存(工厂方法)
- 将动态分配内存的所有权转移给函数
- 对象中保存多态子对象
- 容器中保存指针
传参方式
- 推荐:不涉及所有权,直接传递裸指针
void process(widget *);
- 推荐:获取widget所有权,
void process(unique_ptr<widget>)
- 不推荐:改变unique指向,
void process(unique_ptr<widget>&)
- 不推荐:无意义,
void process(const unique_ptr<widget>&)
- 推荐:不涉及所有权,直接传递裸指针
实战坑点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24struct Node {
int data;
std::unique_ptr<Node> next;
~Node() {
cout << "dtor: " << data << endl;
}
}
// 递归导致的嵌套析构
struct List {
std::unique_ptr<Node> head;
void push(int data) {
head = std::unique_ptr<Node>(new Node{data, std::move(head)});
}
~List() {
while (head) {
head = std::move(head->next);
}
}
}反映出
autoptr
的缺陷- 不支持对数组成员的内存管理
- 失去所有权而导致访问越界(在函数传参的情形下)
- 多次析构同一片空间导致内存崩溃
1 | std::auto_ptr<Stock> ap(new Stock("hello")); |
shared_ptr
- 内存布局(
sizeof
大小为16字节)
由于引用计数块的引入,较之普通的指针,有着一定的成本开销
函数声明
template< class T > class shared_ptr;
- 注意此处较之
unique_ptr
的异同- 二者均可定义删除器,但
shared_ptr
的删除器并不内嵌在型别之中 - 需通过构造函数进行相应的定义
- 原因在于,为了节省
shared_ptr
内存开销
- 二者均可定义删除器,但
特点
- 通过
RAII
机制,实现异常保证 - 拥有动态生命周期的共享对象所有权
- 构造、拷贝 引用计数+1
- 析构 引用计数-1
- 拥有循环引用的问题
- 通过
相关api
release
暴露相应的裸指针,并且释放所有权reset
重置所需管理的所有权- 无参数,仅仅是置空,释放引用计数
- 有参数,置空后指向相应的指针,同时释放引用计数
swap
交换管理的所有权get
返回对象的裸指针use_count
获取引用计数unique
引用计数是否唯一make_shared
构造一个智能指针对象
推荐采用
make_shared
- 拥有更高的安全性
- 在大多数的情形下拥有着更高的效率
推荐的转换方式
static_pointer_cast
dynamic_pointer_cast
const_pointer_cast
reinterpret_pointer_cast
差异
- 采用非虚的父类析构器,在实现多态的时候,不会报错
- 其原因在于,其析构的执行根据类型推断
unique_ptr
不进行类型推断
函数传参方式
1 | // 可行,推荐:成为共享所有权之一 |
shared_ptr
循环引用
1 | class A { |
解决方案,将其中一个
shared_ptr
转换成为weak_ptr
weak_ptr
简要介绍- 核心思想,只负责观察资源的占用情况,不拥有其所有权
- 通过
share_ptr
构造weak_ptr
weak_ptr
拥有引用计数- 通过
expired
判断是否为空 - 通过
use_count
获得引用计数 - 通过
expired()
判断其是否过期 - 通过
lock()
获取被引用对象的shared_ptr
unique_ptr
可转换成shared_ptr
- 切记转换时,采用相关右值(
std::move
) - 本质原因是
unique_ptr
不支持拷贝
- 切记转换时,采用相关右值(
- 切记,若采用
weak_ptr
,需要保障引用计数块的存在- 即采用
make_shared
效率低下情形,由于采用此方案,内存整块分配后,由于weak_ptr
存在,不得释放相关内存,原始对象也无法释放
- 即采用
shared_ptr
局限性
1 |
|
- 如果直接采用
shared_ptr
管理this,会造成double_free
的错误- 主要问题在于,创建了两个控制块,但却管理着同一片内容
- 较为合理的方案
enable_shared_from_this
基本原理- 用法:继承这个玩意
- 不会初始化所需的成员
- 然后返回,
shared_from_this()
- 注意:若T类型不含有Shared指针,则不生成
weak_this
指针
- 用法:继承这个玩意
- 管理内部成员的小技巧
1 | // 栈上的对象不能用智能指针管理 |
Invitation
夕阳的刻痕
781263509
created:03/09/2020
Welcome, myFriend
Use this card to join the candyhome and participate in a pleasant discussion together .
Welcome to Aker’s candyhome,wish you a nice day .
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Aker's Blog!
评论