C++——智能指针

目录

智能指针作用

代码

auto_ptr

特点

模拟实现

 unique_ptr

模拟实现

shared_ptr

模拟实现

 shared_ptr的线程安全

 解决方式:加锁

代码

总结

循环引用

weak_ptr就可以解决这个问题

代码

模拟实现

定制删除器


智能指针作用

更好的解决了多个异常捕获不好解决的状况

代码


#include

using namespace std;

template
class SmartPtr
{
public:

	// 保存资源
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{ }

	//释放资源
	~SmartPtr()
	{
		delete[] _ptr;
		cout << _ptr << endl;
	}

	//像指针一样

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

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

	T& operator[](size_t pos)
	{
		return _ptr[pos];
	}
private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw "除0错误";
	return a / b;
}



void Func()
{
	SmartPtr sp1(new int[10]);
	SmartPtr sp2(new int[10]);

	*sp1 = 10;
	sp1[0]--;

	cout << *sp1 << endl;

	cout << div() << endl;
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* s)
	{
		cout << s << endl;
	}
	return 0;
}

这种方式又叫RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

1.不需要显式地释放资源。
2.采用这种方式,对象所需的资源在其生命期内始终保持有效。

auto_ptr

特点

拷贝构造的特点是资源转移——很多公司禁止auto_ptr。

auto_ptr ap1(new int);
auto_ptr ap2(ap1);

比如以上代码,他会把ap1给清空然后把资源转移给ap2 见图,会导致对象悬空。

C++——智能指针_第1张图片

 

模拟实现

// auto_ptr


	template
	class auto_ptr
	{
	public:

		// 保存资源
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{ }

		//释放资源
		~auto_ptr()
		{
			delete _ptr;
			cout << _ptr << endl;
		}

		auto_ptr(auto_ptr& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr; // ap._ptr被置空了
		}

		auto_ptr& operator=(auto_ptr& ap)
		{
			if (this != &ap)
			{
				delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};

 unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝,直接把拷贝构造给封掉!

C++可以使用在拷贝构造后边加上=delete;

模拟实现


// unique_ptr

	template
	class unique_ptr
	{
	public:

		// 保存资源
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{ }

		//释放资源
		~unique_ptr()
		{
			delete _ptr;
			cout << _ptr << endl;
		}


		// 给拷贝构造封了
		unique_ptr(unique_ptr& up) = delete;



		unique_ptr& operator=(unique_ptr& up)
		{
			if (this != &ap)
			{
				delete _ptr;
				_ptr = up._ptr;
				up._ptr = nullptr;
			}
			return *this;
		}

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};

shared_ptr

 引用计数来解决拷贝构造问题

模拟实现

// shared_ptr

	template
	class shared_ptr
	{
	public:

		// 保存资源
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))// 为引用计数初始化为1
		{ }

		//释放资源
		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}
		void Release()// 释放资源
		{
			if (--(*_pcount) == 0)// 如果当前对象的引用计数为1,则直接释放当前对象
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}

		shared_ptr& operator=(const shared_ptr& sp)
		{
			if (_ptr != sp._ptr) // 如果管理的是同一块资源的两个对象则不需要赋值
			{
				Release();

				_ptr = sp._ptr; // 资源地址赋值
				_pcount = sp._pcount; // 引用计数赋值
				(*_pcount)++;// 引用计数++
			}
		}


		shared_ptr& operator=(shared_ptr& sp)
		{
			if (this != &sp)
			{
				delete _ptr;
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
		int* _pcount;
	};

 shared_ptr的线程安全

// shared_ptr

	template
	class shared_ptr
	{
	public:

		// 保存资源
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))// 为引用计数初始化为1
		{ }

		//释放资源
		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}
		void Release()// 释放资源
		{
			if (--(*_pcount) == 0)// 如果当前对象的引用计数为1,则直接释放当前对象
			{
				//cout << "delete: " << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}

		shared_ptr& operator=(const shared_ptr& sp)
		{
			if (_ptr != sp._ptr) // 如果管理的是同一块资源的两个对象则不需要赋值
			{
				Release();

				_ptr = sp._ptr; // 资源地址赋值
				_pcount = sp._pcount; // 引用计数赋值
				(*_pcount)++;// 引用计数++
			}
		}


		shared_ptr& operator=(shared_ptr& sp)
		{
			if (this != &sp)
			{
				delete _ptr;
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}

		int use_count()
		{
			return *_pcount;
		}

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
		int* _pcount;
	};

	void test_shared_ptr()
	{
		int n = 10000;
		shared_ptr sp1(new int(1));

		thread t1([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr sp2(sp1);
				}
			});

		thread t2([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr sp3(sp1);
				}
			});

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;
	}

 结果应该为1:因为虽然在不停的拷贝构造,但是拷贝的时候引用计数会+1,析构的时候就会-1;最终应该还为1,可结果并非我们想的那样。

见图:
可能会崩溃或者引用计数不为1
这都是线程不安全的体现

C++——智能指针_第2张图片

 解决方式:加锁

代码


	// shared_ptr

	template
	class shared_ptr
	{
	public:

		// 保存资源
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))// 为引用计数初始化为1
			,_pmtx(new mutex)
		{}

		//释放资源
		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			,_pmtx(sp._pmtx)
		{
			_pmtx->lock(); // t1 t2
			++(*_pcount);
			_pmtx->unlock();
		}
		void Release()// 释放资源
		{
			bool flag = false;
			_pmtx->lock();
			if (--(*_pcount) == 0)// 如果当前对象的引用计数为1,则直接释放当前对象
			{
				//cout << "delete: " << _ptr << endl;
				delete _ptr;
				delete _pcount;
				flag = true;
				//delete _pmtx;
			}

			_pmtx->unlock();

			// 如果flag==true说明引用计数减到0了
			if (flag == true)
			{
				delete _pmtx;
			}
		}

		shared_ptr& operator=(const shared_ptr& sp)
		{
			if (_ptr != sp._ptr) // 如果管理的是同一块资源的两个对象则不需要赋值
			{
				Release();

				_ptr = sp._ptr; // 资源地址赋值
				_pcount = sp._pcount; // 引用计数赋值
				_pmtx->lock();
				(*_pcount)++;// 引用计数++
				_pmtx->unlock();

			}
		}


		shared_ptr& operator=(shared_ptr& sp)
		{
			if (this != &sp)
			{
				delete _ptr;
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}

		int use_count()
		{
			return *_pcount;
		}

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmtx;
	};

	void test_shared_ptr()
	{
		int n = 10000;
		shared_ptr sp1(new int(1));

		thread t1([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr sp2(sp1);
				}
			});

		thread t2([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr sp3(sp1);
				}
			});

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;
	}

C++——智能指针_第3张图片
比如我们把类型改为日期类Date:

 


struct Date
	{
		int _year = 0;
		int _month = 0;
		int _day = 0;
	};
	
	void test_shared_ptr()
	{
		int n = 100000;
		shared_ptr sp1(new Date);

		thread t1([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr sp2(sp1);
					sp2->_year++;
					sp2->_month++;
					sp2->_day++;
				}
			});

		thread t2([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr sp3(sp1);
					sp3->_year++;
					sp3->_month++;
					sp3->_day++;
				}
			});

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;


		cout << sp1->_year << endl;
		cout << sp1->_month << endl;
		cout << sp1->_day << endl;
	}

总结

管理的资源不是线程安全的,但是引用计数是线程安全的。

循环引用

关于之前的循环引用的写法是:


struct ListNode
	{
		int val;
		ListNode* _next;
		ListNode* _prev;
	};

	void test_shared_ptr2()
	{
		ListNode* n1 = new ListNode;
		ListNode* n2 = new ListNode;

		n1->_next = n2;
		n2->_prev = n1;

		delete n1;
		delete n2;
	}

如果我们想用智能指针进行进行修改:


	struct ListNode
	{
		int val;
		shared_ptr _next;
		shared_ptr _prev;

		//检测结点是否释放
		~ListNode()
		{
			cout << "delete" << endl;
		}
	};

	void test_shared_ptr2()
	{
		shared_ptr n1 = new(ListNode);
		shared_ptr n2 = new(ListNode);

		n1->_next = n2;
		n2->_prev = n1;

		
	}

会有循环引用的问题,将造成内存泄漏


当n1和n2造成循环引用的条件的时候


当资源对应的引用计数减为0时对应的资源才会被释放,因此资源1的释放取决于资源2当中的prev成员,而资源2的释放取决于资源1当中的next成员。
而资源1当中的next成员的释放又取决于资源1,资源2当中的prev成员的释放又取决于资源2,于是这就变成了一个死循环,最终导致资源无法释放。

weak_ptr就可以解决这个问题

//可以指向/访问资源,不参与资源管理,不增加引用计数

weak_ptr只是shared_ptr的小弟,它根本不支持RAII,他只是辅助shared_ptr

代码


struct ListNode
	{
		int val;


		//可以指向/访问资源,不参与资源管理,不增加引用计数
		weak_ptr _next;
		weak_ptr _prev;

		//检测结点是否释放
		~ListNode()
		{
			cout << "delete" << endl;
		}
	};

	void test_shared_ptr2()
	{
		std::shared_ptr n1(new ListNode);
		std::shared_ptr n2(new ListNode);


		n1->_next = n2;
		n2->_prev = n1;

		cout << n1.use_count() << endl;
		cout << n2.use_count() << endl;
	}

模拟实现


	template
	class weak_ptr
	{
	public:

		// 保存资源
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr)
		{}

		weak_ptr& operator=(const shared_ptr& sp)
		{
			_ptr = sp.get();
			return *this;
		}

		//像指针一样

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

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


	private:
		T* _ptr;
	};


定制删除器

定制删除器的意思是,我们可以传入一个定制删除资源的方法,它就会按照我们想要删除的方式进行删除。

我们可以使用仿函数和lambda表达式来解决


template

struct DeleteArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
		cout << "delete []" << ptr << endl;
	}
};

int main()
{
	//仿函数
	std::shared_ptr sp1(new int[10], DeleteArray());
	std::shared_ptr sp2(new string[10], DeleteArray());
	
	//lambda表达式
	std::shared_ptr sp3(new string[10], [](string* ptr) {delete[] ptr; });

	//文件类型的管理
	std::shared_ptr sp4(fopen("test.cc", "r"), [](FILE* ptr){ fclose(ptr); });



	return 0;
}



	

你可能感兴趣的:(C++,c++,开发语言)