深入理解智能指针,让你爱上智能指针~~

前言引入

上节讲到:
在这里插入图片描述

这样的异常处理起来很麻烦:

p1抛异常, 要处理好p2,就需要先进行对p2的delete, 比较麻烦, 所以使用智能指针

智能指针的引入:
在抛异常之前, 中间的栈帧是正常销毁的, 对象可以正常调用析构函数, 所以智能指针就利用这一点特性.

在这里插入图片描述

在这里插入图片描述

RAII版智能指针

这样的情况, 在智能指针中叫做RAII(资源获得就进行初始化)
在这里插入图片描述

本质的使用方法是使用对操作符的重载进行使用
RAII是智能指针的一种体现(一般是分两个部分的就可以进行这样的处理)
对一个SmartPtr类

创建然后进行简单的使用:

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
此时就会出现错误
在这里插入图片描述
会发现因为浅拷贝, 析构两次, 但是不能写拷贝构造, 因为智能指针是管理这个内存资源, 这边赋值只能希望去共同管理这一份资源, 所以使用引用

auto_ptr

在c++98处理这个的方式是重载构造, 进行管理权转移, 将ptr1管理的权限交给ptr2
在c++98下智能指针叫做auto_ptr

在这里插入图片描述意外访问会崩溃, 因此auto_ptr被不适用, 很多人吐槽

在这里插入图片描述

unique_ptr

因此在 c++98里面, 又使用了unique_ptr
他不让赋值这一步操作正确实现

对拷贝构造函数, 只声明不实现, 这样它编译能正确通过, 函数不会被链接. 但是不排除别人在类外面实现一个无法估量的拷贝构造
为彻底解决, 将他的声明限定为私有, 同时将赋值也设为私有, 就是防止拷贝(以后防止类拷贝的类内就这样写), 不需要实现

在这里插入图片描述
在最新的c++11里面, 对unique_ptr内部进行修改, 也不需要为私有, 直接delete就行

在这里插入图片描述在这里插入图片描述

shared_ptr

但是, 在某些场景下需要拷贝, 所以又引入了share_ptr, 与上述不同在于又引入了count变量在引用计数
version1

在这里插入图片描述

缺点在于:

析构会导致内存泄漏

version2

在这里插入图片描述
缺点在于, 当 ptr2 和ptr1都指向一个时没有问题, 此时若引入第三个智能指针, 那么这个static的count变量就会1, 导致原先的指针计数出现问题

version3

在这里插入图片描述即shared_ptr内部实现原理:

	template <class T>
	class shared_ptr
	{
	public:
	    shared_ptr(T* ptr)//构造函数
	        :_ptr(ptr)
	        , _pcount(new int(1))
	    {}
	
	    shared_ptr(const shared_ptr<T>& sp)//拷贝构造内对计数器+1
	        :_ptr(sp._ptr)
	        , _pcount(sp._pcount)
	    {
	        ++(*_pcount);
	    }
	    ~shared_ptr()
	    {
	        if (--(*_pcount) == 0)//当这个计数器为0时进行析构
	        {
	            cout << "delete->" << _ptr << endl;
	            delete _ptr;
	        }
	    }
	private:
	    T* _ptr;
	    int* _pcount;
	};

main内:


    OK::shared_ptr<string>  sp1(new string("xxxxxxxx"));
    OK::shared_ptr<string> sp2(sp1);

同样的,对=运算符进行重载

shared_ptr<T>& operator= (const shared_ptr<T>& sp)//重载=就是将sp赋值给this
{
	if (--(*_pcount) == 0)//
	{
		delete _pcount;
		delete _ptr;
	}
	_pcount = sp._pcount;
	_ptr = sp._ptr;

	++(*_pcount);

	return *this;
}

在这里插入图片描述
在这里插入图片描述
这样的赋值重载不会排除自己给自己赋值, 这样本身也是不允许的,在刚才的main函数内部, sp1 = sp2, sp3 = sp3,这是不允许的
进行修改

在这里插入图片描述
对比auto_ptr unique_ptr shared_ptr
shared_ptr最实用

智能指针的发展历史

c++98

auto_ptr 管理权转移->设计不好,对象悬空 ( 不建议使用 )

在boost第三方准标准库( 由出++标准委员会推出, 但是比当前的c++语法超前 )
c++11可以说是抄了boost库中的unique_ptr和shared_ptr
但是在boost中叫把unique_ptr叫做scoped_ptr

c++11

unique_ptr ->简单粗暴, 防拷贝

c++11

shared_ptr ->引用计数, 最后一个释放对象的资源->复杂一些, 但是支持拷贝, 非常完美->问题:循环引用

weak_ptr

为此在shared_ptr引入了weak_ptr专门解决循环引用的问题, 但是weak_>ptr不符合RAII, 单纯是解决循环引用的问题
什么是循环引用, 在某些场景下, 需要使用双向链表, 但是为了方便堆内存的释放, 所以使用RAII方式的智能指针来解决这问题, 如下:

在这里插入图片描述
导致内存泄漏:

分析,现在只有n1->next = n2;
在这里插入图片描述

n1节点的next指向n2, n2 n1是包装在shared_ptr内部的ListNode, 通过shared_ptr进行使用,
现在n1的next指向n2的内存
在析构时, 先析构n2这个节点, pcount–,然后再减n1没有问题

在这里插入图片描述
可以看到两个节点都被释放
现在

在这里插入图片描述

两个都有:
我们在运行代码后, 发现节点并没有被释放

在这里插入图片描述

直接崩溃
相比我们自己写的, 库内的shared_ptr也是这样的

在这里插入图片描述

为什么呢?

在这里插入图片描述
这样形成循环, 内存就会被遗漏, 怎么解决这问题?
库内方式(这边只是简单模拟)
在这里插入图片描述
目前可以理解为, 不增加这个引用计数, weak_ptr内部不适用RAII在这里插入图片描述
它使用shared_ptr来进行赋值, 构造操作,专门来解决这个shared_ptr的循环引用问题

可以通过库内的use_count()查看引用计数的次数
使用shared_ptr

在这里插入图片描述

使用weak_ptr()

在这里插入图片描述
weak_ptr简单实现():

template <class T>
class weak_ptr
{
public:
	weak_ptr()
		:_ptr(nullptr)
	{}
	weak_ptr(const shared_ptr<T>& sp) 
		:_ptr(sp.get())
	{

	}
	weak_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		_ptr = sp.get();
		return* this;
	}
	// 像指针一样
	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
	T* get() const
	{
		return _ptr;
	}
	~weak_ptr() {}
private:
	T* _ptr;
};

在这里插入图片描述
有一份shared_ptr创建的资源, 后来也由weak_ptr指向他

在这里插入图片描述

当shared_ptr生命周期到了, weak_ptr没到就会导致野指针访问, 所以给weak_ptr也引入use_count, 这个计数不会增加, 只显示当前的weak_count的计数

在这里插入图片描述
也引入了其他方式, expired(过期)

在这里插入图片描述

检查有没有过期
也有像lock关键字
不让他过期,给他锁住,了解即可

总结

智能指针包含三个方面的东西:
1.RAII

2.像指针一样

3.拷贝问题

在这里插入图片描述

D模板引入(包装器的使用)

包括库内也好, 自己实现也好, 实现的析构不支持delete[], 那么要释放数组应该怎么办呢?
在库内提供这样的方法, 在构造时, 根据提供的模板, 有个D就是处理这个问题
这些个D叫做定制删除器, 他是一个可调用对象, 要给他一个指针, 然后释放资源, 怎么释放, 由我们自己决定
可以使用函数指针, 仿函数, lambda等
在这里插入图片描述
自己实现一下:

在这里插入图片描述在这里插入图片描述

除了这样的删除, 还有其他的一些操作:
对文件的操作

在这里插入图片描述

且lambda更合适

我们自己实现一个, 首先构造函数有两个参数
因为这个参数是构造函数的, 所以在构造函数写一个重载实习一下

在这里插入图片描述
这个D是外部传入的, 所以引入一个包装器参数, 接受这些各种变化,

在这里插入图片描述

在析构时, 使用这个D即可

在这里插入图片描述

问题, 此时单参数就会出现问题,发生未知异常

在这里插入图片描述

为什么呢? 因为我们已经改了析构函数, 所以单参数时, 该对象对_del进行了未初始化的调用, 所以使用缺省值

在这里插入图片描述
而在标准库内不是使用这个方式, 而是将这个类进行另一个类的包装, 然后支持两个参数的构造
这边是包装器更好用, 推荐使用自己的方式包装器来完成, 更加整洁
当然库内的支持的方式更多, 所以也更复杂, 但是在这一个方面, 使用包装器更好

什么是内存泄漏?
不再使用的而(或)未能释放的内存
如何避免:
事后检测, 事前预防

喜欢不防来个三连支持一下~~~

  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温有情

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值