C++智能指针

首先需要了解RAII思想,什么是RAII思想,RAII是一种利用对象生命周期来控制程序资源(内存、文件句柄、网络连接、互斥量)的简单技术。通俗易懂来讲就是在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。就有如下好处:

  • 不需要显式的释放资源
  • 对象所需的资源在其生命周期内始终保持有效
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}

private:
	T* _ptr;
};

上面的SmartPtr不能称之为指针,因为其还不具有指针的行为,指针可以解引用,也可以->去访问所指空间中的内容。

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

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

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

private:
	T* _ptr;
}

所以综上所述智能指针的原理就是RAII的思想加上解引用和访问对象的方法。
AutoPtr

template<class T>
class AutoPtr
{
public:
	AutoPtr(T* ptr = nullptr)
		:_ptr(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;
	}

	~AutoPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}

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

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

private:
	T* _ptr;
};

虽然AutoPtr解决了赋值运算和拷贝构造的问题,但是仍然存在问题,看下面代码:

int main()
{
    int* pa1 = new int;
	int* pa2 = pa1;
	*pa1 = 10;//不会产生任何问题

	AutoPtr<int> sp1(new int);
	*sp1 = 10;
	AutoPtr<int> sp2(sp1);
	//sp1 = 10;//这句话会报错,因为拷贝构造之后sp1已经被释放,所以再进行操作就会报错。
}

AutoPtr所存在的问题:

  • 对象作为被拷贝构造的对象或者被赋值运算的对象后,资源进行转移,再进行使用就会访问越界。

当然这个问题也可以解决,是通过转移释放资源的权力来解决

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

	AutoPtr(const AutoPtr<T>& ap)
		:_ptr(ap._ptr)
		, _owner(ap._owner)
	{
		ap._owner = false;
	}

	AutoPtr<T>& operator=(const AutoPtr<T>& ap)
	{
		if (this != &ap)
		{
			if (_ptr && _owner)
			{
				delete _ptr;
			}

			_ptr = ap._ptr;
			_owner = ap._owner;
			ap._owner = false;
		}
		return *this;
	}	

	~AutoPtr()
	{
		if (_ptr && _owner)
		{
			delete _ptr;
			_ptr = nullptr;
			_owner = false;
		}
	}

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

	T& operator*()
	{
		return *_ptr;
	}
private:
	T* _ptr;
	mutable bool _owner;
};

但是上面的代码仍然存在问题,如执行下面代码:

int main()
{
	AutoPtr<int> ap1(new int);
	AutoPtr<int> ap2(ap1);
	AutoPtr<int> ap3(ap1);
	
	if (1)
	{
		AutoPtr<int> ap4(ap2);
	}
	//当ap4出了自己的作用域,就会释放资源,但是下面又通过ap1对资源进行修改,这是就会称造成野指针。
	*ap1 = 10;
	return 0;
}

所以这样看下来,第二种AutoPtr并没有解决第一种所存在的问题。
那么,为了解决上述的问题,干脆这样,直接让该类不能进行赋值运算和拷贝构造,用起来没有任何问题,简单模拟实现如下:
UniquePtr

template<class T>
class UniquePtr
{
public:
	UniquePtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

	~UniquePtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}

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

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

private:
	UniquePtr(const UniquePtr<T>& up);
	UniquePtr<T>& operator=(const UniquePtr<T>&);
private:
	T* _ptr;
};

void test2()
{
	UniquePtr<int> up1(new int);
	//UniquePtr<int> up2(up1);//已经将拷贝构造重载设为私有
	UniquePtr<int> up2;
	//up2 = up1;//同样的,已经将赋值运算重载设为私有
}

下面是共享类型指针,较上面的SmartPtr多了一个引用计数,实现方式如下:
SharePtr具有如下功能:

  • 可以实现不同类型的资源释放
  • 可以解决多线程资源访问安全问题(加锁实现)
template<class T>
struct DFDef
{
	void operator()(T*& pf)
	{
		delete pf;
		pf = nullptr;
	}
};

template<class T>
struct Free
{
	void operator()(T*& p)
	{
		free(p);
		p = nullptr;
	}
};

struct FClose
{
	void operator()(FILE* pf)
	{
		fclose(pf);
		pf = nullptr;
	}
};

template<class T,class DF = DFDef<T>>
class SharePtr
{
public:
	SharePtr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pCount(nullptr)
		, _pMutex(new mutex)
	{
		if (_ptr)
		{
			_pCount = new int(1);
		}
	}

	SharePtr(const SharePtr<T>& sp)
		:_ptr(sp._ptr)
		, _pCount(sp._pCount)
		, _pMutex(sp._pMutex)
	{
		if (_pCount)
			AddRef();
	}

	//1.sp2没有管理资源
	//2.sp2管理资源,但是没有共享--释放资源以及计数
	//3.sp2管理资源,与其他对象共享--计数-1

	//1.sp1没有资源
	//2.sp2有资源
	SharePtr<T>& operator=(const SharePtr<T>& sp)
	{
		if (this != &sp)
		{
			//管理资源,没有共享
			Release();
			//管理资源也共享
			_ptr = sp._ptr;
			_pCount = sp._pCount;
			if (_pCount)
				AddRef();
		}
		return *this;
	}

	~SharePtr()
	{
		Release();
	}

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

	T& operator*()
	{
		return *_ptr
	}

	int GetCount()
	{
		assert(_pCount);
		return *_pCount;
	}

private:
	void Release()
	{
		if (_ptr && 0 == SubRef())
		{
			DF()(_ptr);
			delete _pCount;
		}
	}

	void AddRef()
	{
		_pMutex->lock();
		++(*_pCount);
		_pMutex->unlock();
	}

	void SubRef()
	{
		_pMutex->lock();
		--(*_pCount);
		_pMutex->unlock();
	}
private:
	T* _ptr;
	int* _pCount;
	mutex* _pMutex;
};

但是前引用会导致一个问题,就是内存泄漏,这里需要引入弱引用与强引用:

  • 强引用:一个强引用是指当被引用的对象仍然或者的话,这个引用也存在,也可以说是只要至少有一个强引用,则个对象就不能被释放。shared_ptr就是强引用
  • 弱引用:当引用的对象活着的时候不一定存在,仅仅是当它自身存在的一个引用。

我们看一段代码:

struct Node
{
	shared_ptr<Node> _pre;
	shared_ptr<Node> _next;

	~Node()
	{
		cout << "~Node" << this << endl;
	}
	int data;
};

void test4()
{
	shared_ptr<Node> Node1(new Node);
	shared_ptr<Node> Node2(new Node);
	Node1->_next = Node2;;
	Node2->_pre = Node1;
	cout << "Node1.use_count" << Node1.use_count() << endl;
	cout << "Node2.use_count" << Node1.use_count() << endl;
}

运行结果是下图(并没有调用析构函数):
在这里插入图片描述
我们同样可以在监视窗口里面看到:
在这里插入图片描述
内存模型可以大致理解为这样:
在这里插入图片描述
将上面代码进行简单修改:

struct Node
{
	weak_ptr<Node> _pre;
	weak_ptr<Node> _next;

	~Node()
	{
		cout << "~Node" << this << endl;
	}
	int data;
};

void test4()
{
	shared_ptr<Node> Node1(new Node);
	shared_ptr<Node> Node2(new Node);
	Node1->_next = Node2;;
	Node2->_pre = Node1;
	cout << "Node1.use_count" << Node1.use_count() << endl;
	cout << "Node2.use_count" << Node1.use_count() << endl;
}

运行结果截然不同:
在这里插入图片描述
我们看到监视窗口内容也发生变化:
在这里插入图片描述
其内存模型也可以理解为如下:
在这里插入图片描述
这里我并没有真正理解weak_ptr的工作原理,所以后面还会更新。

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值