温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

C++11智能指针的具体使用方法

发布时间:2021-08-24 15:15:08 来源:亿速云 阅读:294 作者:chen 栏目:开发技术

本篇内容介绍了“C++11智能指针的具体使用方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

目录
  • 智能指针的原理

    • RAII

    • 智能指针的原理

    • auto_ptr

      • 1.auto_ptr的使用及问题

    • unique_ptr

      • shared_ptr

        • shared_ptr的循环引用

        智能指针的原理

        RAII

        RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

        在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。 借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

        • 不需要显式地释放资源。

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

        我们使用RAII的思想设计SmartPtr类:

        template <class T>
        class SmartPtr
        {
        public:
        	SmartPtr(T* ptr)
        		:_ptr(ptr)
        	{}
        
            ~SmartPtr()
        	{
        		if (_ptr)
        		{
        			delete _ptr;
        			_ptr = nullptr;
        		}
        	}
        
        private:
        	T* _ptr;
        };

        智能指针的原理

        上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容 ,因此:SmartPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

        template <class T>
        class SmartPtr
        {
        public:
        	SmartPtr(T* ptr)
        		:_ptr(ptr)
        	{}
        
        	T& operator*()
        	{
        		return *_ptr;
        	}
        
        	T* operator->()
        	{
        		return _ptr;
        	}
        
            ~SmartPtr()
        	{
        		if (_ptr)
        		{
        			delete _ptr;
        			_ptr = nullptr;
        		}
        	}
        
        private:
        	T* _ptr;
        };

        智能指针使用:

        C++11智能指针的具体使用方法

        总结智能指针的原理:

        • RAII特性

        • 重载operator*和opertaor->,具有像指针一样的行为。

        auto_ptr

        1.auto_ptr的使用及问题

        auto_ptr的头文件#include<memory>

        auto_ptr的使用:

        C++11智能指针的具体使用方法

        C++11智能指针的具体使用方法

        为什么此时访问sp的成员时会报错呢?我们来看看它们的地址。

        C++11智能指针的具体使用方法

        我们发现在拷贝构造之后,sp管理的地址为空,而sp1管理的地址是之前sp所管理的地址,管理权发生了转移。那么上面所说的报错也很容易想通,因为sp管理的地址为空,不能进行访问。

        auto_ptr的问题:当对象拷贝或者赋值后,管理权进行转移,造成前面的对象悬空。auto_ptr问题是非常明显的,所以实际中很多公司明确规定了不能使用auto_ptr。

        auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份AutoPtr来了解它的原理:

        template<class T>
        class AutoPtr
        {
        public:
        	AutoPtr(T* ptr)
        		:_ptr(ptr)
        	{}
        
        	//拷贝:管理权转移
        	AutoPtr(AutoPtr<T> &sp)
        		:_ptr(sp._ptr)
        	{
        		sp._ptr = nullptr;
        	}
        
        	//赋值:管理权转移
        	AutoPtr& operator=(AutoPtr<T> &sp)
        	{
        		if (this != &sp)
        		{
        			if (_ptr)
        				delete _ptr;
        			_ptr = sp._ptr;
        			sp._ptr = nullptr;
        		}
        		return *this;
        	}
        
        	~AutoPtr()
        	{
        		if (_ptr)
        		{
        			delete _ptr;
        			_ptr = nullptr;
        		}
        	}
        
        	T* operator->()
        	{
        		return _ptr;
        	}
        
        	T& operator*()
        	{
        		return *_ptr;
        	}
        
        private:
        	T* _ptr;
        };

        unique_ptr

        为了解决拷贝或者赋值时管理权转移的问题,出现了unique_ptr。

        unique_ptr解决问题的方式非常粗暴:防拷贝,也就是不让赋值和拷贝

        unique_ptr的使用:

        C++11智能指针的具体使用方法

        unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理:

        template<class T>
        class UniquePtr
        {
        public:
        
        	UniquePtr(T* ptr)
        		:_ptr(ptr)
        	{}
        
        	// C++11防拷贝的方式:delete
        	UniquePtr(const UniquePtr<T> &) = delete;
        
        	UniquePtr& operator=(const UniquePtr<T>&) = delete;
        
        	T* operator->()
        	{
        		return _ptr;
        	}
        
        	T& operator*()
        	{
        		return *_ptr;
        	}
        
        	~UniquePtr()
        	{
        		if (_ptr)
        		{
        			delete _ptr;
        			_ptr = nullptr;
        		}
        	}
        
        private:
        
        	//C++98防拷贝的方式:只声明不实现+声明成私有
        	//UniquePtr(UniquePtr<T> const &);
        	//UniquePtr& operator=(UniquePtr<T> const &);
        
        	T* _ptr;
        };

        shared_ptr

        c++11中提供更靠谱的并且支持拷贝的shared_ptr

        shared_ptr的使用

        C++11智能指针的具体使用方法

        shared_ptr中拷贝与赋值都是没有问题的。

        shared_ptr的原理

        • shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。

        • 对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一

        • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

        • 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

        shared_ptr中成员函数:use_count(对象数据的引用计数)

        示例:

        C++11智能指针的具体使用方法

        示例详解:

        C++11智能指针的具体使用方法

        利用引用计数简单的实现SharedPtr,了解原理:

        template<class T>
        class SharedPtr
        {
        public:
        	SharedPtr(T* ptr)
        		:_ptr(ptr)
        		,_count(new int(1))
        	{}
        
        	SharedPtr(const SharedPtr<T> &sp)
        		:_ptr(sp._ptr)
        		,_count(sp._count)
        	{
        		//计数器累加
        		++(*_count);
        	}
        
        	SharedPtr& operator=(const SharedPtr<T> &sp)
        	{
        		//判断管理的是否是同一份资源
        		if (_ptr != sp._ptr)
        		{
        			//计数-1,判断之前管理的资源是否需要释放
        			if ((--(*_count)) == 0)
        			{
        				delete _ptr;
        				delete _count;
        			}
        			
        			_ptr = sp._ptr;
        			_count = sp._count;
        
        			//计数器累加
        			++(*_count);
        		}
        		return *this;
        	}
        
        	T* operator->()
        	{
        		return _ptr;
        	}
        	
        	T& operator*()
        	{
        		return *_ptr;
        	}
        
        	~SharedPtr()
        	{
        		if (--(*_count) == 0)
        		{
        			delete _ptr;
        			delete _count;
        			_ptr = nullptr;
        			_count = nullptr;
        		}
        	}
        
        private:
        	T* _ptr;
        	int* _count;//给每份资源开辟一个计数器
        };

        但是还存在一个线程安全的问题:

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

        2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

        这里我们通过加锁来解决线程安全问题:

        template<class T>
        class SharedPtr
        {
        public:
        	SharedPtr(T* ptr)
        		:_ptr(ptr)
        		,_count(new int(1))
        		,_mutex(new mutex)
        	{}
        
        	SharedPtr(const SharedPtr<T> &sp)
        		:_ptr(sp._ptr)
        		,_count(sp._count)
        		,_mutex(sp._mutex)
        	{
        		//计数器累加
        		AddCount();
        	}
        
        	SharedPtr& operator=(const SharedPtr<T> &sp)
        	{
        		//判断管理的是否是同一份资源
        		if (_ptr != sp._ptr)
        		{
        			//计数-1,判断之前管理的资源是否需要释放
        			if (SubCount() == 0)
        			{
        				delete _ptr;
        				delete _count;
        				delete _mutex;
        			}
        			
        			_ptr = sp._ptr;
        			_count = sp._count;
        			_mutex = sp._mutex;
        
        				//计数器累加
        			AddCount();
        		}
        		return *this;
        	}
        
        	//线程安全的累加器
        	int AddCount()
        	{
        		//加锁
        		_mutex->lock();
        		++(*_count);
        		_mutex->unlock();
        		return *_count;
        	}
        
        	int SubCount()
        	{
        		_mutex->lock();
        		--(*_count);
        		_mutex->unlock();
        		return *_count;
        	}
        
        	T* operator->()
        	{
        		return _ptr;
        	}
        	
        	T& operator*()
        	{
        		return *_ptr;
        	}
        
        	~SharedPtr()
        	{
        		if (SubCount() == 0)
        		{
        			delete _ptr;
        			delete _count;
        			delete _mutex;
        			_ptr = nullptr;
        			_count = nullptr;
        			_mutex = nullptr;
        		}
        	}
        	
        private:
        	T* _ptr;
        	int* _count;//给每份资源开辟一个计数器
        	mutex* _mutex; //每一份资源有一个独立的锁
        };

        shared_ptr的循环引用

        循环引用的场景:

        struct ListNode
        {
        	int _data; 
            shared_ptr<ListNode> _prev;
        	shared_ptr<ListNode> _next;
        	~ListNode() { cout << "~ListNode()" << endl; }
        };
        
        void test()
        {
        	shared_ptr<ListNode> node1(new ListNode);
        	shared_ptr<ListNode> node2(new ListNode);
        
        	node1->_next = node2;
        	node2->_prev = node1;
        }

        C++11智能指针的具体使用方法

        node1和node2两个智能指针对象指向两个节点,两个节点的引用计数都是1。node1->next指向node2,node2->prev指向node1,两个节点的引用计数都变成2。程序运行完之后,析构node1和node2,node1和node2所指向的节点引用计数分别减1,但是node1->next指向下面节点,node2->prev指向上面节点,此时,两个节点的引用计数都为1,所以两个节点不能析构。

        引用计数为0时,如果要析构node1节点,就先要去析构node1中的自定义结构,然后再析构node1。也就是说node1->next析构了,node2就释放了;node2->prev析构了,node1就释放了。但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

        解决方案:
        在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
        原理就是,node1->_next = node2和node2->_prev = node1时weak_ptr的_next和_prev不会增加node1和node2的引用计数

        weak_ptr最大作用就是解决shared_ptr的循环引用

        struct ListNode
        {
        	int _data; 
            weak_ptr<ListNode> _prev;
        	weak_ptr<ListNode> _next;
        	~ListNode() { cout << "~ListNode()" << endl; }
        };
        void test()
        {
        	shared_ptr<ListNode> node1(new ListNode);
        	shared_ptr<ListNode> node2(new ListNode);
        
        	node1->_next = node2;
        	node2->_prev = node1;
        }

        注意:
        weak_ptr不能单独使用,可以用shared_ptr创建

        	//weak_ptr错误使用
        	weak_ptr<ListNode> node1(new ListNode);
        
        	//weak_ptr正确使用
        	shared_ptr<ListNode> node2(new ListNode);
        	weak_ptr<ListNode> node3(node2);

        “C++11智能指针的具体使用方法”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

        向AI问一下细节

        免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

        c++
        AI