• C++智能指针


    ●🧑个人主页:你帅你先说.
    ●📃欢迎点赞👍关注💡收藏💖
    ●📖既选择了远方,便只顾风雨兼程。
    ●🤟欢迎大家有问题随时私信我!
    ●🧐版权:本文由[你帅你先说.]原创,CSDN首发,侵权必究。

    1.为什么需要智能指针?

    #include 
    #include 
    #include 
    using namespace std;
    int* p1 = new int;
    int* p2 = nullptr, * p3 = nullptr;
    int div()
    {
    	int a = 3, b = 0;
    
    	if (b == 0)
    		throw invalid_argument("除0错误");
    	//若此时抛出了异常, 那下面这段代码就没法执行了,就会导致内存泄漏
    	delete p1;
    	delete p2;
    	delete p3;
    	return a / b;
    }
    void func()
    {
    	
    	try
    	{
    		p2 = new int;
    		p3 = new int;
    
    		cout << div() << endl;
    	}
    	catch (...)
    	{
    
    	}
    
    }
    int main()
    {
    	func();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    智能指针用来解决内存泄漏异常安全问题

    2.智能指针的使用及原理

    2.1RAII

    RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
    在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

    • 不需要显式地释放资源。
    • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

    2.2智能指针原理

    使用RAII思想设计智能指针

    template<class T>
    class SmartPtr
    {
    public:
    	//构造函数
    	SmartPtr(T* ptr):_ptr(ptr)
    	{}
    	//析构函数
    	~SmartPtr()
    	{
    		cout << "delete:" << _ptr << endl;
    		delete _ptr;
    	}
    	//重载*运算符
    	T& operator*()
    	{
    		return *_ptr;
    	}
    	//重载->运算符
    	T* operator->()
    	{
    		return _ptr;
    	}
    private:
    	T* _ptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    智能指针的原理:
    1.RAII特性
    2.重载operator*和opertaor->,具有像指针一样的行为。

    2.3常见的智能指针

    2.3.1auto_ptr

    实现原理:管理权转移
    我们直接来看模拟实现

    template<class T>
    class auto_ptr
    {
    public:
    	//构造函数
    	auto_ptr(T* ptr):_ptr(ptr)
    	{}
    	//拷贝构造函数
    	auto_ptr(auto_ptr<T>& sp)
    		:_ptr(sp._ptr)
    	{
    		// 管理权转移
    		sp._ptr = nullptr;
    	}
    	//析构函数
    	~auto_ptr()
    	{
    		if (_ptr)
    		{
    			cout << "delete:" << _ptr << endl;
    			delete _ptr;
    		}
    	}
    
    	T& operator*()
    	{
    		return *_ptr;
    	}
    
    	T* operator->()
    	{
    		return _ptr;
    	}
    private:
    	T* _ptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    但实际上auto_ptr在实际中很少使用,因为它的设计有缺陷。

    2.3.2unique_ptr

    实现原理:防拷贝

    template<class T>
    class unique_ptr
    {
    public:
    	//构造函数
    	unique_ptr(T* ptr):_ptr(ptr)
    	{}
    	//析构函数
    	~unique_ptr()
    	{
    		if (_ptr)
    		{
    			cout << "delete:" << _ptr << endl;
    			delete _ptr;
    		}
    	}
    
    	T& operator*()
    	{
    		return *_ptr;
    	}
    
    	T* operator->()
    	{
    		return _ptr;
    	}
    	//防止拷贝
    	unique_ptr(const unique_ptr<T>& sp) = delete;
    private:
    	T* _ptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    但即便这样设计还是有问题,万一在某种场景下我需要拷贝。所以就有了接下来的shared_ptr。

    2.3.3shared_ptr

    实现原理:通过引用计数的方式来实现多个shared_ptr对象之间共享资源

    template<class T>
    class shared_ptr
    {
    public:
    	shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex)
    	{}
    
    	shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx)
    	{
    		AddRef();
    	}
    
    	void Release()
    	{
    		//加锁保证线程安全
    		_pmtx->lock();
    
    		bool flag = false;
    		if (--(*_pRefCount) == 0 && _ptr)
    		{
    			cout << "delete:" << _ptr << endl;
    			delete _ptr;
    			delete _pRefCount;
    
    			flag = true;
    		}
    
    		_pmtx->unlock();
    
    		if (flag == true)
    		{
    			delete _pmtx;
    		}
    	}
    
    	void AddRef()
    	{
    		_pmtx->lock();
    
    		++(*_pRefCount);
    
    		_pmtx->unlock();
    	}
    
    	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    	{
    		//if (this != &sp)
    		//这里不能写成this != &sp,因为可能会出现指向同一个对象的指针相互赋值,这样就没有意义了,下面这样写可以提高效率。
    		if (_ptr != sp._ptr)
    		{
    			Release();
    
    			_ptr = sp._ptr;
    			_pRefCount = sp._pRefCount;
    			_pmtx = sp._pmtx;
    			AddRef();
    		}
    
    		return *this;
    	}
    
    	int use_count()
    	{
    		return *_pRefCount;
    	}
    
    	~shared_ptr()
    	{
    		Release();
    	}
    
    	T& operator*()
    	{
    		return *_ptr;
    	}
    
    	T* operator->()
    	{
    		return _ptr;
    	}
    
    	T* get() const
    	{
    		return _ptr;
    	}
    private:
    	T* _ptr;
    	int* _pRefCount;
    	mutex* _pmtx;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    几点注意:

    1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
    2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
    3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
    4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其它对象就成野指针了。

    2.3.4weak_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;
    	}
    private:
    	T* _ptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    你会发现weak_ptr的设计相较于shared_ptr其实就是取消了引用计数。
    循环引用场景:

    struct ListNode
    {
    	int _val;
    	std::shared_ptr<ListNode> _next;
    	std::shared_ptr<ListNode> _prev;
    	~ListNode()
    	{
    		cout << "~ListNode()" << endl;
    	}
    };
    
    int main()
    {
    	std::shared_ptr<ListNode> n1(new ListNode);
    	std::shared_ptr<ListNode> n2(new ListNode);
    	// 循环引用
    	n1->_next = n2;
    	n2->_prev = n1;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这段代码你可能没看出来什么问题,一张图带你了解。
    在这里插入图片描述

    1. n1和n2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
    2. n1的_next指向n2,n2的_prev指向n1,引用计数变成2。
    3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点且_prev还指向上一个节点。也就是说_next析构了,n2就释放了,_prev析构了,node1就释放了。
    4. 但是_next属于n1的成员,n1释放了,_next才会析构,而n1由_prev管理,_prev属于n2成员,这就是循环引用,谁也不会释放。

    解决方案:把结构体内的shared_ptr换成weak_ptr就可以解决了。

    struct ListNode
    {
    	int _val;
    	std::shared_ptr<ListNode> _next;
    	std::shared_ptr<ListNode> _prev;
    	~ListNode()
    	{
    		cout << "~ListNode()" << endl;
    	}
    };
    
    int main()
    {
    	std::shared_ptr<ListNode> n1(new ListNode);
    	std::shared_ptr<ListNode> n2(new ListNode);
    	// 循环引用
    	n1->_next = n2;
    	n2->_prev = n1;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    喜欢这篇文章的可以给个一键三连点赞👍关注💡收藏💖

  • 相关阅读:
    2023年北京市安全员-C3证证模拟考试题库及北京市安全员-C3证理论考试试题
    认识RocketMQ4.x架构设计
    短视频创作有什么建议吗?直接上干货
    高数---级数
    NISP网络安全中渗透测试的流程是什么
    高分辨率相机成像效果就越好吗?
    网络协议--IGMP:Internet组管理协议
    Todo List
    doccano安装问题
    【考研数学】九. 无穷级数
  • 原文地址:https://blog.csdn.net/qq_52363432/article/details/126138730