• 智能指针的理解


    为什么要定义智能指针?

    之前定义指针申请内存空间使用后要进行delete进行资源释放,但是如果在资源释放之前就抛出异常,则不会进行正常的资源释放,造成资源泄露的问题。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
    例如:

    void MemoryLeaks()
    {
    // 1.内存申请
    int* p1 = new int[10];
    throw;
    
    delete[] p1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    因此通过定义智能指针,利用对象的生命周期来控制程序资源,在生命周期结束时自动释放资源。

    智能指针的原理

    RALL

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

    • 不需要显式地释放资源。
    • 采用这种方式,对象所需的资源在其生命期内始终保持有效。
    //智能指针:
    //1.实现RALL思想
    //2.实现指针功能:*,->
    template <class T>
    class AutoPtr
    {
    public:
    	//RALL
    	//构造函数获取资源的管理权
    	AutoPtr(T* ptr)
    		:_ptr(ptr)
    	{}
    	//析构中释放资源
    	~AutoPtr()
    	{
    		if (_ptr)
    			delete _ptr;
    	}
    
    	T* operator->()
    	{
    		return _ptr;
    	}
    
    	T&  operator*()
    	{
    		return *_ptr;
    	}
    
    	//管理权转移
    	AutoPtr(AutoPtr<T>& ap)
    		:_ptr(ap.ptr)
    	{
    		ap._ptr = nullptr;
    	}
    
    	AutoPtr<T>& operator=(AutoPtr<T>& ap)
    	{
    		if (this != &ap)
    		{
    			if (_ptr)
    				delete _ptr;
    			//管理权转移
    			_ptr = ap._ptr;
    			ap._ptr = nullptr;
    		}
    		return *this;
    	}
    private:
    	T* _ptr;
    };
    
    class A
    {
    public:
    	int  _a = 10;
    };
    
    void test()
    {
    	错误写法:该写法容易造成资源二次释放
    	//int* ptr = new int;
    	//SmartPtr sp(ptr);
    	//SmartPtr sp2(ptr);
    	//正确写法:
    	AutoPtr<int> sp1(new int);
    	//SmartPtr sp2((int*)malloc(sizeof(int)));
    	AutoPtr<A> sp2(new A);
    	
    	//智能指针,编译器调用 析构自动释放内存,不存在内存泄露的问题
    	*sp1 = 10;
    	sp2->_a = 100;
    	(*sp2)._a = 1000;
    
    	//普通指针,手动释放内存,存在内存泄漏的问题
    	int* p = new int;
    	A* pa = new A;
    	*p = 1;
    	pa->_a = 5;
    	(*pa)._a = 55;
    	delete p;
    	delete pa;
    }
    
    • 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

    智能指针的使用

    std::auto_ptr

    C++98版本的库中就提供了auto_ptr的智能指针。
    auto_ptr的实现原理:管理权转移的思想。
    拷贝时直接将新指针指向被拷贝对象资源,被拷贝对象指针悬空。

    	auto_ptr<int> sp1(new int);
    	auto_ptr<int> sp2(sp1); // 管理权转移
     	//sp1悬空
    
    • 1
    • 2
    • 3

    std::unique_ptr

    unique_ptr的实现原理:简单粗暴的防拷贝。

    	//unique_ptr:防拷贝
    	unique_ptr<int> up(new int);
    	//unique_ptr的拷贝构造为删除函数
    	//unique_ptr up2(up);
    	unique_ptr<int> up3(new int);
    	//unique_ptr赋值运算符为删除函数
    	//up3 = up;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    std::shared_ptr

    C++11中开始提供更靠谱的并且支持拷贝的shared_ptr.

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

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

    循环引用分析:

    1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动
      delete。
    2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
    3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
    4. 也就是说_next析构了,node2就释放了。
    5. 也就是说_prev析构了,node1就释放了。
    6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。
    struct ListNode
    {
    	shared_ptr<ListNode> _next;
    	shared_ptr<ListNode> _prev;
    	int _data;
    
    	~ListNode()
    	{
    		cout << "~ListNode" << endl;
    	}
    };
    void test1()
    {
    	shared_ptr<ListNode> n1(new ListNode);
    	shared_ptr<ListNode> n2(new ListNode);
    
    	cout << n1.use_count() << endl;
    	cout << n2.use_count() << endl;
    
    	n1->_next = n2;
    	n2->_prev = n1;
    
    	cout << n1.use_count() << endl;
    	cout << n2.use_count() << endl;
    }
    
    • 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

    使用weak_ptr可以在拷贝时不进行计数+1操作,因此可以正常调用析构,进行资源释放。

    struct ListNode
    {
    	weak_ptr<ListNode> _next;
    	weak_ptr<ListNode> _prev;
    	int _data;
    
    	~ListNode()
    	{
    		cout << "~ListNode" << endl;
    	}
    };
    void test1()
    {
    	shared_ptr<ListNode> n1(new ListNode);
    	shared_ptr<ListNode> n2(new ListNode);
    
    	cout << n1.use_count() << endl;
    	cout << n2.use_count() << endl;
    
    	n1->_next = n2;
    	n2->_prev = n1;
    
    	cout << n1.use_count() << endl;
    	cout << n2.use_count() << endl;
    }
    
    • 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
  • 相关阅读:
    【MySQL】根据查询结果更新统计值
    【服务器数据恢复】HP EVA存储设备多块硬盘出现故障的数据恢复案例
    第3章 列表简介
    136. 只出现一次的数字
    去除angular中blob图片显示报unsafe的错误提示
    安全防御——IDS(入侵检测系统)
    音视频技术-声反馈啸叫的产生与消除
    Spring集成hazelcast实现分布式缓存
    3DGS语义分割之LangSplat
    (51单片机)习题:TM1640驱动数码管并用任意矩阵键盘调节显示参数
  • 原文地址:https://blog.csdn.net/m0_49619206/article/details/134307857