智能指针、循环引用、锁、删除器

目录

智能指针使用场景的分析

RAII策略和智能指针的设计思路

RAII介绍

四大智能指针

shared_ptr

常见构造(不含删除器)

资源拷贝:

资源移动:

use_count

shared_ptr的循环引用问题

shared_ptr循环引用产生的原因:

unique_ptr

对比shared_ptr

weak_ptr

解决循环引用问题

加锁

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

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

总图:

auto_ptr

删除器

shared_ptr删除器:

unique_ptr删除器:

两种智能指针的删除器对比​编辑


智能指针使用场景的分析

在资源管理中"指针"维护指向资源,涉及到的内存管理与异常分析十分重要。而在指针的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

复习必看:gitee

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 |
| 删除器类型 | 模板参数 | 类型擦除 |
| 运行时开销 | 通常无(若删除器为空) | 有(需要间接调用) |
| 编译器优化 | 可内联 | 难以内联 |

相关推荐
水云桐程序员5 小时前
C++官方文档获取平台
c++·学习方法
星浩AI5 小时前
OpenAI 大神 Karpathy 开源:用 Obsidian 实现 LLM Wiki 知识库管理方法
后端·openai·agent
Sylvia-girl5 小时前
C++模板【上】
开发语言·c++
2zcode5 小时前
基于MATLAB多特征融合与SVM的金属表面缺陷检测系统
开发语言·支持向量机·matlab
hacker7075 小时前
Visual Studio安装教程(C#开发版)
ide·c#·visual studio
2zcode5 小时前
基于MATLAB脑电信号的帕金森病抑郁症检测研究
开发语言·matlab·抑郁症·帕金森病
王老师青少年编程5 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【哈夫曼贪心】:荷马史诗
c++·算法·贪心·csp·信奥赛·哈夫曼贪心·荷马史诗
untE EADO5 小时前
Java进阶之路,Java程序员职业发展规划
java·开发语言
样例过了就是过了5 小时前
LeetCode热题100 最小路径和
c++·算法·leetcode·动态规划