智能指针的说明与简单实现

本博客代码在vs2022底下调试与实现

目录

为什么需要智能指针?

RAII

auto_ptr 

auto_ptr的实现

 对auto_ptr的研究

98版auto_ptr(上面代码)的问题

后来修正的auto_ptr的问题

unique_ptr

unique_ptr实现

shared_ptr

shared_ptr的实现

shared_ptr的问题

解决问题的办法


为什么需要智能指针?

因为我们很容易对于指针管理的资源忘记释放,这就很容易造成资源泄漏

RAII

提到智能指针,就不得不提一下RAII,这是一种思想

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

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

说白了,就是把资源进行包装,利用析构函数的特性,来帮助我们释放资源

auto_ptr 

auto_ptr的实现

#pragma once

namespace czz {

	template <class T>
	class auto_ptr {
	public:
		
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _isOwner(false)
		{
			if (_ptr != nullptr) {
				_isOwner = true;
			}
		}

		~auto_ptr() {
			if (_ptr != nullptr && _isOwner == true) {
				delete _ptr;
				_ptr = nullptr;
			}
		}

		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}

		//解决资源释放的问题:资源转移
		auto_ptr(const auto_ptr<T>& ap)
			:_ptr(ap._ptr)
			, _isOwner(ap._isOwner) {

			ap._isOwner = false;
		}

		auto_ptr<T>& operator=(const auto_ptr<T>& ap) {
			if (this != &ap) {
				if (_ptr != nullptr && _isOwner == true) {
					delete _ptr;
				}
				_ptr = ap._ptr;
				_isOwner = ap._isOwner;
				ap._isOwner = false;
			}
			return *this;
		}

	private:
		T* _ptr;
		//_isOwner有两个用处,判断资源是否存在,以及此对象是否拥有释放权;
		mutable bool _isOwner;
	};

}

 对auto_ptr的研究

先谈谈历史,auto_ptr是c++98里面提出的概念,在后来的修正中改过一次,但是最后又改回去了.后来标准库给的建议是:任何情况下都不建议使用.

那既然改来改去,就表示有问题嘛,而且还没有得到解决,所以才建议任何情况下都不建议使用.

98版auto_ptr(上面代码)的问题

我们知道,一个类,要有拷贝构造和赋值运算符重载,那对于包装的指针,浅拷贝肯定不行,因为它涉及到了资源的管理,如果两个对象用一个资源,那一个释放资源以后,另一个怎么办?所以98版解决这个问题的办法就是资源转移,就是拷贝构造或者赋值运算符重载的时候,把前一个对象的资源交给后一个对象,但这也有一个问题:两个指针不能同时指向同一个资源.

后来修正的auto_ptr的问题

修正,自然就是解决前面的问题,那是怎么修正的呢,就是按照我上面的代码,也就是大家都能使用,但只有一个有资格释放,但是这下问题更大了,可能直接就造成野指针了,比如前面已经把资源释放了,后面又用指向这个资源的其他的指针,所以c++11又改回去了.那c++11到底搞了什么,让人们放弃使用auto_ptr呢?往下看.

unique_ptr

unique_ptr实现

简单的让大家看个思想

#pragma once
namespace czz {
	template <class T>
	class unique_ptr {
	public:
		unique_ptr(T* ptr) 
			:_ptr(ptr)
		{}
		~unique_ptr() {
			if (_ptr != nullptr) {
				delete _ptr;
				_ptr = nullptr;
			}
		}

		unique_ptr(const unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;

		T& operator*() {
			return *_ptr;
		}
		T* operator->() {
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

相信大家也能看懂,就是直接不允许实现拷贝构造和赋值运算符重载,这个没什么好说的,好用,唯一可惜的就是一份资源只能由一个指针管理

shared_ptr

这个是为了解决上面的那个问题而实现的,对于资源问题,也就是涉及到拷贝构造和赋值运算符重载,它采用的就是引用计数,先看代码

shared_ptr的实现

#pragma once
#include <mutex>
namespace czz {
	template<class T>
	struct DFDef {
		void operator()(T*& ptr) {
			if (ptr) {
				delete ptr;
				ptr = nullptr;
			}
		}
	};
	template <class T, class DF = DFDef<T>>  //可能资源不是new的,所以需要别的方法去释放
	class shared_ptr {
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr), _pRefCount(nullptr), _pMutex(nullptr) {
			if (_ptr) {
				_pRefCount = new int(1);
				_pMutex = new std::mutex;
			}
		}

		~shared_ptr() {
			Release();
		}

		T& operator*() {
			return *_ptr;
		}

		T* operator->() {
			return _ptr;
		}

		shared_ptr(const shared_ptr<T, DF>& sp)
			:_ptr(sp._ptr), _pRefCount(sp._pRefCount) {
			_pMutex = new std::mutex;
			AddRef();
		}

		shared_ptr<T, DF>& operator=(const shared_ptr<T, DF>& sp) {
			if (_ptr != sp._ptr) {
				Release();
				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				_pMutex = new std::mutex;
				AddRef();
			}
			return *this;
		}

		size_t use_count()const {
			return *_pRefCount;
		}

		T* Get() {
			return _ptr;
		}
	private:
		void AddRef() {
			if (_pRefCount != nullptr) {
				_pMutex->lock();
				++(*_pRefCount);
				_pMutex->unlock();
			}
		}

		int SubRef() {
			_pMutex->lock();
			--(*_pRefCount);
			_pMutex->unlock();
			return *_pRefCount;
		}

		void Release() {
			if (_ptr != nullptr && SubRef() == 0) {
				DF()(_ptr);
				delete _pRefCount;
				delete _pMutex;
				_ptr = nullptr;
				_pRefCount = nullptr;
			}
		}
	private:
		T* _ptr;
		int* _pRefCount;
		std::mutex* _pMutex;
	};
}

shared_ptr的问题

我们举个栗子:

对于一个双向链表的一个节点,我们可以把里面它本身和它里面的两个指向前(prev)后(next)的指针都改成shared_ptr,现在有一个链表有两个节点,A节点的next指向B节点,B节点的prev指向A节点,那现在不要B节点了,它的引用计数减1,但因为A节点的next还指向这B节点的东西,所以B节点的资源不能直接释放,同理,A节点也一样,而且推导下去,你会发现,这种情况下,无论如何都无法释放这两个资源

解决问题的办法

weak_ptr:这个就是专门解决shared_ptr的,也很简单,就是只有主资源采用shared_ptr,其他的采用weak_ptr就行了,在上面的例子里面,对于节点本身,我们用shared_ptr管理,节点里面的如prev和next,我们采取weak_ptr就可以了,至于原理,大概说一下,就是引用计数其实不是一个,而是两个,一个管理shared_ptr,一个管理weak_ptr

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值