C++_智能指针

发布于:2024-04-19 ⋅ 阅读:(20) ⋅ 点赞:(0)


前言

智能指针是一种采用RAII思想来保护申请内存不被泄露的方式来管理我们申请的内存,对于RAII,我们之前也已经有过接触,在学习异常和guard_mutex都有过接触RAII思想。

今天我们将RAII运用到指针就是智能指针。


一、智能指针原理

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

int main()
{
	SmartPtr<int> ip = new int;
	return 0;
}

运行结果
在这里插入图片描述
就算我们没有在main函数主动调用delete,因为类对象出了生命周期自动调用析构函数的delete,这样保证了我们内存泄露的风险。

二、库支持的智能指针类型

1.std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针
auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr。

template<class T>
class auto_ptr
{
public:
	auto_ptr(T* ptr)
		:_ptr(ptr)
	{}
	auto_ptr(auto_ptr<T>& sp)
		:_ptr(sp._ptr)
	{
		// 管理权转移
		sp._ptr = nullptr;
	}
	auto_ptr<T>& operator=(auto_ptr<T>& ap)
	{
		// 检测是否为自己给自己赋值
		if (this != &ap)
		{
			// 释放当前对象中资源
			if (_ptr)
				delete _ptr;
			// 转移ap中资源到当前对象中
			_ptr = ap._ptr;
			ap._ptr = nulllptr;
		}
		return *this;
	}
	~auto_ptr()
	{
		if (_ptr)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
		}
	}
	// 像指针一样使用
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};
int main()
{
	auto_ptr<int> ap1 = new int(1);
	auto_ptr<int> ap2 = ap1;
	std::cout << *ap1 << std::endl; // 由于管理权转移,ap1._ptr已经是一个nullptr
	return 0;
}

在这里插入图片描述

auto_ptr 的实现方式乍一看没什么问题,但实际运用上,会里会有一个大坑。
导致的原因就是拷贝构造和赋值重载的管理权转移,有人可能会说,那我在使用了拷贝构造和赋值重载之后不再使用被拷贝和赋值的变量不就可以了,虽说如此,但是对于不太了解auto_ptr的人来说,这里还可以对访问被拷贝和赋值的变量会很坑。

2.std::unique_ptr

基于auto_ptr的不成功,C++11中开始提供更靠谱的unique_ptr。
那么它是否解决了auto_ptr的问题呢?

template<class T>
class unique_ptr
{
public:
	unique_ptr(T* ptr)
		:_ptr(ptr)
	{}
	~unique_ptr()
	{
		if (_ptr)
		{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
		}
	}
	// 像指针一样使用
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	unique_ptr(const unique_ptr<T>& sp) = delete;

	unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
	T* _ptr;
};

unique_ptr 则是直接将拷贝构造和赋值重载设置为delete,防止拷贝和赋值。

那有没有支持拷贝和赋值的智能指针呢?

3.std::shared_ptr

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

shared_ptr是通过计数的方式来做到允许拷贝和赋值并且不会出现auto_ptr的情况。

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

	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
	{
		++(*_pcount); //增加计数
	}

	~shared_ptr()
	{
		if (--(*_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
			std::cout << "delete _ptr  delete _pcount; " << std::endl;
		}
	}
	shared_ptr<T>& operator= (const shared_ptr<T>& sp)
	{
		if (sp._ptr == _ptr) return *this;
		if (--(*_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
		}
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		++(*_pcount);
		return *this;
	}

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

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

	T* get() const
	{
		return _ptr;
	}

private:
	T* _ptr;
	int* _pcount;
};

int main()
{
	 shared_ptr<int> sp1(new int);
	 shared_ptr<int> sp2(sp1);
	 shared_ptr<int> sp3(sp1);
	
	 shared_ptr<int> sp4(new int);
	 shared_ptr<int> sp5(sp4);
	
	 sp1 = sp1;

	 sp1 = sp2;

	 sp1 = sp4;

	 sp2 = sp4;

	 sp3 = sp4;	

	 *sp1 = 2;

	 *sp2 = 3;
	 return 0;
}

运行结果没有问题。

上面的示例代码其实还有问题的,因为是通过计数的方式来保证唯一才释放,我们学习过多线程,我们就能看出,这并不是一个线程安全的类,一旦设计到多线程,同时修改_pcount的数据,就会发生数据紊乱的问题,所以我们就可以添加锁来进行保护。

template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))
		,_pmtx(new std::mutex)
	{}

	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
		, _pmtx(sp._pmtx)
	{
		AddRef();
	}

	~shared_ptr()
	{
		Release();
	}
	shared_ptr<T>& operator= (const shared_ptr<T>& sp)
	{
		if (sp._ptr == _ptr) return *this;
		Release();
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		_pmtx = sp._pmtx;
		AddRef();
		return *this;
	}

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

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

	T* get() const
	{
		return _ptr;
	}

	int use_count()
	{
		return *_pcount;
	}

	void AddRef()
	{
		_pmtx->lock();
		++(*_pcount);
		_pmtx->unlock();
	}

	void Release()
	{
		_pmtx->lock();
		bool flag = false;
		if (--(*_pcount) == 0 && _ptr)
		{
			std:: cout << "delete:" << _ptr << std::endl;
			delete _ptr;
			delete _pcount;
			flag = true;
		}
		_pmtx->unlock();
		if (flag == true)
		{
			delete _pmtx;
		}
	}

private:
	T* _ptr;
	int* _pcount;
	std::mutex* _pmtx;
};

struct Date
{
	int _year = 0;
	int _month = 0;
	int _day = 0;
};
void SharePtrFunc(shared_ptr<Date>& sp, size_t n, std::mutex& mtx)
{
	std::cout << sp.get() << std::endl;
	for (size_t i = 0; i < n; ++i)
	{
		// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
		shared_ptr<Date> copy(sp);
		// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最终看到的结果,并一定是加了2n
		{
			std::unique_lock<std::mutex> lk(mtx);
			copy->_year++;
			copy->_month++;
			copy->_day++;
		}
	}
}
int main()
{
	shared_ptr<Date> p(new Date);
	std::cout << p.get() << std::endl;
	const size_t n = 100000;
	std::mutex mtx;
	std::thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx));
	std::thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx));
	t1.join();
	t2.join();
	std::cout << p->_year << std::endl;
	std::cout << p->_month << std::endl;
	std::cout << p->_day << std::endl;
	std::cout << p.use_count() << std::endl;
	return 0;
}

在这里插入图片描述

4.std::weak_ptr

shared_ptr其实也有问题,就比如在双向链表中,在析构时,会出现计数永远无法为1的情况,这就导致无法析构。所以就有了weak_ptr来解决这一问题。

struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}

在这里插入图片描述
解决方案则是使用weak_ptr,因为weak_ptr仅仅只是指向资源,不进行管理。

template<class T>
class weak_ptr  //weak_ptr的特点就是指向资源但是不进行管理
{
public:
	weak_ptr()
		:_ptr(nullptr)
	{}
	weak_ptr(const shared_ptr<T>& sp)
		:_ptr(sp.get())
	{}
	weak_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		_ptr = sp.get();
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

struct ListNode

{
	int _data;
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;
	~ListNode() { std::cout << "~ListNode()" << std::endl; }
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	std::cout << node1.use_count() << std::endl;
	std::cout << node2.use_count() << std::endl;
	node1->_next = node2;
	node2->_prev = node1;
	std::cout << node1.use_count() << std::endl;
	std::cout << node2.use_count() << std::endl;
	return 0;
}

三、删除器

在上面的示例代码中,我们所演示的智能指针的对象的创建都是使用new 来申请空间, 可是如果我们不使用new 的方式来构造_ptr呢? 我们使用new Data[10] 来申请一个数组空间呢? 我们使用fopen来打开文件,让智能指针来管理我们的文件指针呢? 而这里我们的析构函数不管对于什么情况都是采用delete的方式来释放资源,当类型不匹配时,就会出现问题。

所以就有删除器。

template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))
		, _pmtx(new std::mutex)
	{}

	template<class D>
	shared_ptr(T* ptr, D del)
	: _ptr(ptr)
	, _pcount(new int(1))
	, _pmtx(new std::mutex)
	, _del(del)
	{}

	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
		, _pmtx(sp._pmtx)
	{
		AddRef();
	}

	~shared_ptr()
	{
		Release();
	}
	shared_ptr<T>& operator= (const shared_ptr<T>& sp)
	{
		if (sp._ptr == _ptr) return *this;
		Release();
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		_pmtx = sp._pmtx;
		AddRef();
		return *this;
	}

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

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

	T* get() const
	{
		return _ptr;
	}

	int use_count()
	{
		return *_pcount;
	}

	void AddRef()
	{
		_pmtx->lock();
		++(*_pcount);
		_pmtx->unlock();
	}

	void Release()
	{
		_pmtx->lock();
		bool flag = false;
		if (--(*_pcount) == 0 && _ptr)
		{
			std::cout << "delete:" << _ptr << std::endl;
			_del(_ptr);
			delete _pcount;
			flag = true;
		}
		_pmtx->unlock();
		if (flag == true)
		{
			delete _pmtx;
		}
	}

private:
	T* _ptr;
	int* _pcount;
	std::mutex* _pmtx;
	std::function<void(T*)> _del;
};

// 仿函数的删除器
template<class T>
struct FreeFunc {
	void operator()(T* ptr)
	{
		std::cout << "free:" << ptr << std::endl;
		free(ptr);
	}
};
template<class T>
struct DeleteArrayFunc {
	void operator()(T* ptr)
	{
		std::cout << "delete[]" << ptr << std::endl;
		delete[] ptr;
	}
};
int main()
{
	FreeFunc<int> freeFunc;
	std::shared_ptr<int> sp1((int*)malloc(4), freeFunc);
	DeleteArrayFunc<int> deleteArrayFunc;
	std::shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
	std::shared_ptr<int> sp3(new int[10], [](int* p) 
		{
			delete[] p; 
			std::cout << "lambda -> delete[]" << std::endl;
		});
	std::shared_ptr<FILE> sp4(fopen("test.txt", "w"), [](FILE* p)
		{
			fclose(p); 
			std::cout << "lambda -> fclose(p)" << std::endl; 
		});
	return 0;
}

在这里插入图片描述

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。