• 【C/C++】智能指针



    在这里插入图片描述

    1.智能指针的原理

    1.1RAII

    RAII叫做资源获取即初始化,是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。 通常是将资源封装到一个类中,将资源管理的责任交给一个对象。

    • 不需要显式地释放资源。
    • 采用这种方式,对象所需的资源在其生命期内始终保持有效
    1.2实现一个自己的智能指针
    template<class T>
    class smartprt
    {
    public:
    	smartprt(T* ptr)
    		:_ptr(ptr)
    	{}
        smartptr(smartptr<T>&ptr)
    		:_ptr(ptr)
    	{}
    	~smartprt()
    	{
    		cout << "delete:" << ptr << endl;
    		delete _ptr; //释放内存
    		_ptr = nullptr;
    	}
    	T& operator*()
    	{
    		return *_ptr;
    	}
    	T* operator->()
    	{
    		return _ptr;
    	}
    	T* get()
    	{
    		return _ptr;
    	}
    private:
    	T* _ptr;
    };
    

    测试

    struct Date
    {
    	int _year;
    	int _month;
    	int _day;
    };
    int main()
    {
    	smartptr<int>ptr1(new int);
    	*ptr1 = 10;
    	cout << *ptr1 << endl;
    	smartptr<Date>date(new Date);
        /*
        	这一步编译器进行了优化,原调用应该是
        	date.operator->()->_year;  编译器为了可读性
        	省略了operator->()的调用
        */
    	date->_year = 2022;
    	date->_month = 9;
    	date->_day = 22;
    	cout << "year:" << date->_year << endl;
    	cout << "_month:" << date->_month << endl;
    	cout << "_day:" << date->_day << endl;
    	return 0;
    }
    

    在这里插入图片描述

    1.2.1拷贝出现的二次析构问题

    ​ 上面smartptr类,会出现二次析构问题。(比如我们拷贝一个指针,那么指针指向的内存可能被释放多次)

    void func()
    {
    	smartptr<Date>date1(new Date);
    	date1->_year = 2022;
    	date1->_month = 9;
    	date1->_day = 22;
    	smartptr<Date>date3 = date1;
    }
    int main()
    {
    	func();
    	return 0;
    }
    

    在这里插入图片描述

    2.标准库中的智能指针

    2.1std::auto_ptr

    ​ C++98版本的库中就提供了auto_ptr的智能指针。其核心思想是将内存的管理权移交。【结果是无论如何拷贝,都只有一个指针可以对内存进行管理】。

    下面实现一个简化版本的auto_ptr

    template<class T>
    class myauto_ptr
    {
    	typedef myauto_ptr<T> self;
    public:
    	//构造函数
    	myauto_ptr(T*ptr):_ptr(ptr)
    	{}
    	~myauto_ptr()
    	{
    		if (_ptr)
    		{
    			cout << "delete" << _ptr << endl;
    			delete _ptr;
    			_ptr = nullptr;
    		}
    	}
    	//拷贝构造,移交管理权
    	myauto_ptr(myauto_ptr<T>&au)
    		:_ptr(au._ptr)
    	{
    		au._ptr = nullptr;
    	}
        //赋值运算符重载
        myauto_ptr<T>& operator=(myauto_ptr<T>&aut)
    	{
    		_ptr = aut._ptr;
    		aut._ptr = nullptr;
    		return *this;
    	}
    	T& operator*()
    	{
    		return *_ptr;
    	}
    	T* operator->()
    	{
    		return _ptr;
    	}
    	T* get()
    	{
    		return _ptr;
    	}
    private:
    	T* _ptr;
    };
    

    测试

    void fun2()
    {
    	myauto_ptr<Date>date1(new Date);
    	date1->_year = 2022;
    	date1->_month = 9;
    	date1->_day = 22;
    	myauto_ptr<Date>date2(date1);
    	cout << "date2->_year" << date2->_year << endl;
    	cout << "date2->month" << date2->_month << endl;
    	cout << "date2->day" << date2->_day << endl;
    	cout << "date1->_year" << date1->_year << endl;
    	cout << "date1->month" << date1->_month << endl;
    	cout << "date1->day" << date1->_day << endl;
    }
    int main()
    {
    	fun2();
    	return 0;
    }
    

    在这里插入图片描述

    表明只有一个指针具有管理和访问内存的权限

    2.2std::unique_ptr

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

    template<class T>
    class myunique_ptr
    {
    public:
    	//构造函数
    	myunique_ptr(T* ptr) :_ptr(ptr)
    	{}
    	~myunique_ptr()
    	{
    		if (_ptr)
    		{
    			cout << "delete" << _ptr << endl;
    			delete _ptr;
    			_ptr = nullptr;
    		}
    	}
    	//防止拷贝
        //方法一:使用关键字delete
    	//方法二:自己在类中声明并实现什么都不干的拷贝构造和赋值运算符重载
    	myunique_ptr(const myunique_ptr<T>& q) = delete;
    	myunique_ptr<T>& operator=(const myunique<T>& sp) = delete;
    	T& operator*()
    	{
    		return *_ptr;
    	}
    	T* operator->()
    	{
    		return _ptr;
    	}
    	T* get()
    	{
    		return _ptr;
    	}
    private:
    	T* _ptr;
    };
    
    2.3std::shared_ptr

    C++中第一个好用的智能指针。

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

    • shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
    • 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减1
    • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
    • 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
    template<class t>
    class myshared_ptr
    {
    public:
    	void release()
    	{
    		if (--(*_count) == 0 && _ptr)
    		{
    			cout << "delete" << _ptr << endl;
    			delete _ptr;
    			_ptr = nullptr;
    			delete _count;
    			_count = nullptr;
    		}
    	}
    	myshared_ptr(t* ptr)
    		:_ptr(ptr)
    		,_count(new int(1))
    	{}
    	~myshared_ptr()
    	{
    		release();
    	}
    	t& operator*()
    	{
    		return *_ptr;
    	}
    	t* operator->()
    	{
    		return _ptr;
    	}
    	t* get()
    	{
    		return _ptr;
    	}
    	myshared_ptr(const myshared_ptr<t>& sp)
    		:_ptr(sp._ptr)
    		, _count(sp._count)
    	{
    		(*_count)++;
    	}
    	//赋值运算符重载
    	myshared_ptr<t>& operator=(const myshared_ptr<t>& sp)
    	{
    		//if (this != &sp)
    		if (_ptr != sp._ptr)
    		{
    			release();
    			_ptr=sp._ptr;
    			_count =sp._count;
    			++(*_count);
    		}
    		return *this;
    	}
    private:
    	t* _ptr;
    	int* _count;
    };
    

    测试

    void fun3()
    {
    	myshared_ptr<Date>date1(new Date);
    	date1->_year = 2022;
    	date1->_month = 9;
    	date1->_day = 22;
    	myshared_ptr<Date>date2(date1);
    	myshared_ptr<Date>date3 = date1;
    	cout << "date2->_year" << date2->_year << endl;
    	cout << "date2->month" << date2->_month << endl;
    	cout << "date2->day" << date2->_day << endl;
    	cout << "date1->_year" << date1->_year << endl;
    	cout << "date1->month" << date1->_month << endl;
    	cout << "date1->day" << date1->_day << endl;
    }
    int main()
    {
    	fun3();
    	return 0;
    }
    

    在这里插入图片描述

    2.4shared_ptr的线程安全问题
    1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是
    2. 这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。
    3. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

    解决方法:对_count的操作都加锁

    template<class t>
    class myshared_ptr
    {
    public:
    	void release()
    	{
            _pmtx->lock();
    		if (--(*_count) == 0 && _ptr)
    		{
    			cout << "delete" << _ptr << endl;
    			delete _ptr;
    			_ptr = nullptr;
    			delete _count;
    			_count = nullptr;
    		}
            _pmtx->unlock();
    	}
    	myshared_ptr(t* ptr)
    		:_ptr(ptr)
    		,_count(new int(1))
             , _pmtx(new mutex)   
    	{}
    	~myshared_ptr()
    	{
    		release();
    	}
    	t& operator*()
    	{
    		return *_ptr;
    	}
    	t* operator->()
    	{
    		return _ptr;
    	}
    	t* get()
    	{
    		return _ptr;
    	}
    	myshared_ptr(const myshared_ptr<t>& sp)
    		:_ptr(sp._ptr)
    		, _count(sp._count)
             , _pmtx(sp._pmtx)
    	{
             _pmtx->lock();
    		(*_count)++;
            _pmtx->unlock();
    	}
    	//赋值运算符重载
    	myshared_ptr<t>& operator=(const myshared_ptr<t>& sp)
    	{
    		//if (this != &sp)
    		if (_ptr != sp._ptr)
    		{
    			release();
    			_ptr=sp._ptr;
    			_count =sp._count;
                _pmtx->lock();
    			++(*_count);
                _pmtx->unlock();
    		}
    		return *this;
    	}
    private:
    	t* _ptr;
    	int* _count;
        mutex* _pmtx;
    };
    
    2.5循环引用和weak_ptr
    2.5.1循环引用

    shared_ptr虽然功能交其他的智能指针功能更加强大,但是同样有其问题,其中一个就是循环引用的问题。如:

    struct ListNode
    {
    	std::shared_ptr<ListNode> prev = nullptr;
    	std::shared_ptr<ListNode> next = nullptr;
    	int _val = 0;
    	~ListNode()
    	{
    		cout << "~ListNode()" << endl;
    	}
    };
    int main()
    {
    	std::shared_ptr<ListNode>ptr1(new ListNode);
    	std::shared_ptr<ListNode>ptr2(new ListNode);
    	ptr1->next = ptr2;
    	ptr2->prev = ptr1;
    	return 0;
    }
    

    按照预期,ptr1和ptr2析构时会分别释放对应的ListNode结构,但结果是并没有释放内存

    在这里插入图片描述

    shared_ptr的底层是实现的引用计数,上面的代码逻辑为:

    在这里插入图片描述

    当main函数结束,ptr1和ptr2发生析构时:

    在这里插入图片描述

    2.5.2weak_ptr

    为了解决shared_ptr出现的循环引用问题,C++11引入了weak_ptr。

    weak_ptr可以访问对应的内存,但是不参与资源的管理。

    struct ListNode
    {
    	std::weak_ptr<ListNode> prev ;
    	std::weak_ptr<ListNode> next ;
    	int _val = 0;
    	~ListNode()
    	{
    		cout << "~ListNode()" << endl;
    	}
    };
    int main()
    {
    	std::shared_ptr<ListNode>ptr1(new ListNode);
    	std::shared_ptr<ListNode>ptr2(new ListNode);
    	ptr1->next = ptr2;
    	ptr2->prev = ptr1;
    	return 0;
    }
    

    在这里插入图片描述

    2.5.3weak_ptr模拟实现

    weak_ptr的底层实现很简单,可以访问对应的内存,但是不参与资源的管理。

    template <class T>
    class myweak_ptr
    {
        myweak_ptr()
            :_ptr(nullptr)
         {}
       	myweak_ptr(const shared_ptr<T>& sp)
        	:_ptr(sp.get())
        {}
        myweak_ptr<T>& operator(const shared_ptr<T>&sp)
        {
            if(_ptr!=sp.get())
            {
                _ptr=sp.get();
            }
            return *this;
        }
    private:
        T* _ptr;
    }
    

    3.定制删除器

    智能指针默认释放资源的方式是delete。

    由于无法删除数组类型的指针或者FILE*的类型,所以出现了定制删除器。

    struct  Date
    {
    	~Date()
    	{
    		cout << "~Date()" << endl;
    	}
    private:
    	int _year = 1;
    	int _month = 1;
    	int _day = 1;
    };
    int main()
    {
    	std::unique_ptr<Date>date1(new Date);
    	std::unique_ptr<Date>date2(new Date[10]);
    	std::unique_ptr<Date>date3((Date*)malloc(sizeof(Date) * 5));
    	//std::unique_ptrptr1((FILE*)fopen("test.cpp", "r"));
    	return 0;
    }
    

    在这里插入图片描述

    发生了内存泄漏

    定制删除器的底层就是一个仿函数

    struct  Date
    {
    	~Date()
    	{
    		cout << "~Date()" << endl;
    	}
    private:
    	int _year = 1;
    	int _month = 1;
    	int _day = 1;
    };
    
    template <class T>
    struct  del
    {
    	void operator()(T* ptr)
    	{
    		delete[] ptr;
    	}
    };
    template <class T>
    struct  freed
    {
    	void operator()(T* ptr)
    	{
    		free(ptr);
    	}
    };
    int main()
    {
    	std::unique_ptr<Date>date1(new Date);
    	std::unique_ptr<Date,del<Date>>date2(new Date[10]);
    	std::unique_ptr<Date,freed<Date>>date3((Date*)malloc(sizeof(Date) * 10));
    	//std::unique_ptrptr1((FILE*)fopen("test.cpp", "r"));
    	return 0;
    }
    

    在这里插入图片描述

    delete的底层是先调用析构函数,在调用operator delete。而operator delete又封装了free()

  • 相关阅读:
    搜维尔科技:【案例分享】Xsens用于工业制造艺术创新设计平台
    windows服务器热备、负载均衡配置
    轻量应用服务器部署vue项目
    使用Vue的transition组件写一个数字滚动竟然如此简单
    MPLS基础
    算法金 | Python 中有没有所谓的 main 函数?为什么?
    假期题目整合
    Linux基本指令(中)
    【LeetCode】No.78. Subsets -- Java Version
    为什么曲面函数的偏导数可以表示其曲面的法向量?
  • 原文地址:https://blog.csdn.net/qq_53893431/article/details/127104482