目录
Ⅲ.sp2指针也移走指向"3333333"原引用计数-1(1->0)自动析构、wp指针仍维护原资源。
[Ⅳ.wp弱指针移动(wp内资源自动析构),wp移至sp1处引用计数+1(1->2),auto sp3 = wp.lock();以sp3加锁维护wp与sp1共同指向的资源引用计数+1(1->2)](#Ⅳ.wp弱指针移动(wp内资源自动析构),wp移至sp1处引用计数+1(1->2),auto sp3 = wp.lock();以sp3加锁维护wp与sp1共同指向的资源引用计数+1(1->2))
智能指针使用场景的分析
在资源管理中"指针"维护指向资源,涉及到的内存管理与异常分析十分重要。而在指针的new与delete(尤其是对于非内置类型的指针)时往往会不可避免的出现抛异常情况。
|----------------|-----------|
| 异常类型 | 触发场景 |
| std::bad_alloc | new分配失败 |
| SIGSEGV | 空指针/野指针访问 |
| SIGSEGV | 越界/非法访问内存 |
| SIGABRT | 双重释放 |
在设计了try catch语句的基础上我们可以使用其来捕获与抛出,但事实情况是指针的申请与释放具备高密度性和复合性,我们再使用传统的try catch来处理代码的结构性和可读性都会被极大地损害。
基于以上原因设计智能指针来自动化管理指针类型。
他们有:unique_ptr、shared_ptr、weak_ptr及auto_ptr。
RAII策略和智能指针的设计思路
RAII介绍
RAII(Resources Acquisition Is Initailization译作:资源获取即初始化),将资源获取分配给一个对象,接着控制对资源的访问。资源在生命周期内始终保持有效,最后在对象析构时释放资源,保证了资源的正常释放避免了资源的泄漏问题。
智能指针在满足RAII的设计思路下,还要方便资源的访问,所以智能指针会像迭代器类一样,重载operator*/operator->/operator[]等运算符,方便资源访问。
四大智能指针
shared_ptr
C++11引进,译作共享指针。正常支持资源拷贝 与资源移动 ,底层应用了引用计数的方式实现。
常见构造(不含删除器)
cpp
shared_ptr<T> sp1(new int(999));
shared_ptr<T[]> sp2(new T[n]{..................});
shared_ptr<T> msp(move(sp1));//shared_ptr的"支持移动"
资源拷贝:
资源拷贝实际上是再创建一指针与shared_ptr指向同一内容、引用计数加1



资源移动:
移动(move)后原指针为空


use_count
用于返回share_ptr引用计数的数据。


shared_ptr的循环引用问题
前置条件:
一定是先外界的指针先销毁导致(*_pcount)--从2变为1 如图:

外界销毁完后要进行内部的销毁:

假设先销毁B内的内容:
- 先对sp1操作。
- 对sp1销毁要先把sp2销毁否则导致内存泄漏。
- 找到sp2销毁,对sp1发现相同的内存泄漏风险。
至此循环形成导致二者内存均无法释放。
shared_ptr循环引用产生的原因:
shared_ptr同时在节点外与节点内使用,采用"引用计数"的方式来简化销毁流程。
所以我们应该避免使用shared_ptr设计出存在循环引用的逻辑。
unique_ptr
C++11引进,译作唯一指针。**不支持拷贝,只支持移动。**不需要拷贝的场景就非常建议使用它。
对比shared_ptr
|-------|------------|------------|
| x64系统(8字节) |||
| 特性 | unique_ptr | shared_ptr |
| 资源所有权 | 唯一独占 | 经常共享 |
| 引用计数 | 无 | 有 |
| 拷贝语义 | 不支持拷贝 | 支持拷贝 |
| 移动语义 | 支持移动 | 也支持移动 |
| 内存开销 | 8字节(单指针) | 16字节(双指针) |
| 性能对比 | 极佳 | 较佳 |
其底层实现仅仅8字节大小与不支持拷贝 只支持移动的属性支持意味着它是独占资源的最常用维护性指针。
简单使用:
cpp
int main()
{
unique_ptr<int> up1(new int(0));
unique_ptr<int[]> up2(new int[100]());
unique_ptr<int[]> up3(move(up2));//支持移动
//unique_ptr<int[]> up4(up2);不支持拷贝
//unique_ptr<int[]> up5 = up2;//不支持拷贝构造
return 0;
}
unique_ptr是最常用的智能指针。
weak_ptr
C++11引进,译作"弱指针"。
"弱"理解为:具有指针基本性质(+自动释放)的"观察指针 "。
主要作用是与shared_ptr配合时不影响引用计数其次是资源检测 +资源加锁控制。
解决循环引用问题
作为内部指针解决循环引用问题:

于是:

weak_ptr的底层简化版:
cpp
template<typename T>
class weak_ptr
{
public:
struct control_block
{
int strong_ref;//强引用计数
int weak_ref;//弱引用计数
};
private:
T* _ptr; //指向对象
control_block* cb;//指向内存块
};
总之weak_ptr是通过再加一层的引用计数来进行决策释放机制,此机制保证了内存安全。
加锁
前言:
此加锁仅演示加锁过程。
库函数expired(){return use_count == 0;} 空true,非空false。
加锁代码,下有图解
cpp
int main()
{
// 1. 创建 shared_ptr 和 weak_ptr
std::shared_ptr<string> sp1(new string("111111"));
std::shared_ptr<string> sp2(sp1);
std::weak_ptr<string> wp = sp1;
cout << "--- 1. 初始状态 wp 信息 ---" << endl;
cout << wp.expired() << endl; // 是否过期
cout << wp.use_count() << endl; // 引用计数
// 2. sp1 指向新资源
cout << "\n--- 2. sp1 指向 \"222222\" ---" << endl;
sp1 = make_shared<string>("222222");
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
// 3. sp2 也指向新资源
cout << "\n--- 3. sp2 指向 \"333333\" ---" << endl;
sp2 = make_shared<string>("333333");
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
// 4. wp 重新绑定到 sp1,并 lock()
cout << "\n--- 4. wp = sp1,调用 lock() ---" << endl;
wp = sp1;
auto sp3 = wp.lock(); // 提升为 shared_ptr
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
// 5. sp1 指向新资源,wp 指向的资源只剩 sp3
cout << "\n--- 5. sp1 指向 \"444444\" ---" << endl;
sp1 = make_shared<string>("444444");
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
return 0;
}
Ⅰ.2+1指针维护

Ⅱ.sp1指向新对象、sp2引用计数-1(2->1)


Ⅲ.sp2指针也移走指向"3333333"原引用计数-1(1->0)自动析构、wp指针仍维护原资源。


Ⅳ.wp弱指针移动(wp内资源自动析构),wp移至sp1处引用计数+1(1->2),auto sp3 = wp.lock();以sp3加锁维护wp与sp1共同指向的资源引用计数+1(1->2)


Ⅴ.sp1移走指向新资源,被加锁的sp3仍维护原资源。


总图:

auto_ptr
源于C++98是失败的设计---拷贝原理:++拷贝/赋值++ 时将被拷贝对象的资源转移到拷贝对象,导致原指针置空。C++11弃用、C++17移除

为什么是失败的设计?
与本来目的不符:原目的是多指针共同维护资源,但一旦使用auto_ptr就导致原对象失去资源管理能力。
删除器
删除器是针对于智能指针(仅限于shared_ptr与unique_ptr),在生命周期结束后调用的相应的可调用对象来(有bind返回的绑定对象、函数指针、lamda表达式、仿函数)销毁资源的方式。
内置类型(new int/char等):出生命周期自动结束。
非内置类型:new char[] 、new string、new vector等,调用的是相应非内置类型的析构函数。
因此就出现了对自定义类型的讨论,对于自定义类型/特殊类型,我们就需要手动指定删除器来进行特定调用要求。
shared_ptr删除器:
删除器位于非模板参数
常用的有lambda表达式函数指针
cpp
//shared_ptr的外部调用删除器
//函数指针
void fptr(int* a) { delete a; }
//labdam表达式
auto lambda = [](int* a) {delete a; };
void testShared_ptr()
{
std::shared_ptr<int> sp1(new int(999), fptr);
//写fptr()认为调用函数的"返回值"
std::shared_ptr<int> sp2(new int(99), lambda);
//内嵌lambda的表达式(推荐)
std::shared_ptr<int> sp3(new int(8), [](int* a) {delete a; });
}
构造函数重载删除器

unique_ptr删除器:
void(*)(int) // 函数指针:指向一个返回void、接受int参数的函数
void* // 数据指针:指向未知类型数据的指针
定义位于模板参数内

cpp
//unique_ptr的模板类型删除器
//函数指针
void my_deleter(int* p) { delete p; }// my_deleter 的类型是 void(int*)
//lambda表达式
auto deleter = [](int* p) {delete p; };
//仿函数
struct fileDeleter
{
void operator()(FILE* f)const
{
fclose(f);
}
};
void testUniqueptr()
{
std::unique_ptr<int, void(*)(int*)> up1(new int(6), my_deleter);
// decltype推导lambda表达式的类型
std::unique_ptr<int, decltype(deleter)> up2(new int(7), deleter);
// 记住这样lambda的使用形式
std::unique_ptr<FILE, fileDeleter> up3(fopen("test.txt", "r"));
}
两种智能指针的删除器对比
cpp
// unique_ptr:删除器是类型的一部分
template<typename T, typename Deleter = std::default_delete<T>>
class unique_ptr;
// shared_ptr:删除器被类型擦除(不体现在模板参数中)
template<typename T>
class shared_ptr;
|-------|-------------|------------|
| 特性 | unique_ptr | shared_ptr |
| 删除器类型 | 模板参数 | 类型擦除 |
| 运行时开销 | 通常无(若删除器为空) | 有(需要间接调用) |
| 编译器优化 | 可内联 | 难以内联 |