参考文章:
《现代 C++:一文读懂智能指针》 https://zhuanlan.zhihu.com/p/150555165
《当我们谈论shared_ptr的线程安全性时,我们在谈论什么》 https://zhuanlan.zhihu.com/p/416289479
指针
指针是C++中一种内置变量类型,存放内存地址
// 局部变量,存储空间静态分配
int a;
int *p = &a;
delete p; // 报错
// 内存动态分配,用指针存储内存首地址
int *p = new int(42);
// 释放内存:只有动态内存管理中的地址指针才能delete
delete p;
内存管理
(1)静态内存分配:局部变量、全局/静态变量,不需要且不能手动释放的内存。静态内存分配的缺陷是内存使用不够灵活,不能及时释放内存,内存使用效率底
(2)动态内存管理方式:直接内存管理(new/delete)、智能指针管理(unique_ptr/shared_ptr)
- 直接内存管理:new/malloc主动申请的内存,资源存放于堆/内存映射区,指针接收 new 分配的内存空间首地址;delete + 指针名 释放内存,可以也必须手动释放内存
- delete释放内存,会调用内存中类的析构函数,如果类中依然存在动态资源,则在析构函数中通过delete进一步删除。直接内存管理的缺陷是易出现内存泄漏、野指针
- 智能指针管理内存:指针变量生命周期结束时自动释放指针指向的资源,安全性更高
智能指针细节分析
智能指针封装为类,用来智能管理内存,指针对象生命周期结束时自动释放其管理的资源,不需要手动释放
API:定义、初始化、拷贝赋值、其他函数接口,忽略,不记得可以去查书
与普通指针一样,智能指针既可指向动态申请的内存,也可指向静态局部变量
- 指向动态分配内存空间时默认使用 delete 释放资源
- 指向静态资源时,无法通过 delete 释放资源,需要自定义删除器。删除器指定了在指针生命周期结束时要对静态资源做什么操作(RAII的体现)
shared_ptr
shared_ptr共享式管理资源,允许多个shared_ptr指向同一资源
shared_ptr基本原理:通过指针共享资源,指向资源的指针数量为0时自动释放资源
// 通过 shared_ptr 管理的资源有两种初始化方式
// 使用 new 初始化
std::shared_ptr<T> ptr1 (new T);
// 使用 make_shared 初始化智能指针
std::shared_ptr<T> ptr2 = make_shared<T>();
(1)定义的指针对象(存放在栈中)中有两个内置指针成员变量,它们分别指向:共享资源、控制信息
(2)通过new初始化智能指针,会动态分配两次内存,两块内存分别存放:共享资源、控制信息
控制信息中包含:指向共享资源的shared_ptr/weak_ptr的个数(引用计数)、默认/自定义删除器、指向共享资源的指针;
通过make_shared初始化智能指针,内存只分配一次,共享资源与控制信息存放到同一块内存中,且控制信息中不必再存放指向共享资源的指针。
weak_ptr:unique_ptr的替代,指向资源的shared_ptr数量为0时,不管有没有weak_ptr指向资源,均会释放。解决unique_ptr循环引用问题。
线程安全
线程安全否?多线程操作同一个shared_ptr时是否会出现不一致的情况?例如有两个线程对同一个shared_ptr进行赋值或拷贝,引用计数会不会只加了1?
考虑赋值过程:
std::shared_ptr<int> sptr1 = std::make_shared<int>(42);
std::shared_ptr<int> sptr2 = std::make_shared<int>(42);
std::shared_ptr<int> sptr3(sptr1); // 情形1:sptr1 管理的对象引用计数加一
sptr1 = sptr2; // 情形2:sptr1 重新指向 sptr2管理的对象
情形1:管理同一个数据的shared_ptr在进行引用计数的增加或减少时是线程安全的,因为对引用计数的操作是原子操作
情形2:考虑赋值过程,sptr1管理对象的引用计数减一,sptr2管理对象的引用计数加一,这个过程也是线程安全的
再考虑智能指针管理的数据的线程安全性。例如多线程通过shared_ptr 共享同一vector,对其进行push_back可能造成内存错误(多线程并发引起)
unique_ptr
unique_ptr独占它管理的资源。
{
// 初始化unique_ptr
unique_ptr<int> p1 (new int(42));
// unique_ptr不支持普通的拷贝与赋值
unique_ptr<int> p2(p1); // 错误,不支持拷贝
unique_ptr<int> p3;
p3 = p1; // 错误,不支持赋值
}
一个例外:当unique_ptr是一个将亡值(返回值)时,可以对其进行拷贝或将其值赋给新的 unique_ptr。或者使用std::move将unique_ptr主动转化为一个右值(将亡值),然后可进行拷贝或赋值:
{
std::unique_ptr<int> uptr = std::make_unique<int>(200);
std::unique_ptr<int> uptr1 = uptr; // 编译错误,std::unique_ptr<T> 是 move-only 的
std::unique_ptr<int> uptr2 = std::move(uptr); // uptr将亡时才能拷贝它 或被其赋值
assert(uptr == nullptr);
}
标签:std,管理,内存,shared,unique,ptr,指针
From: https://www.cnblogs.com/essays/p/17461884.html