【C++】智能指针

发布于:2024-11-04 ⋅ 阅读:(131) ⋅ 点赞:(0)

一、智能指针的使用及原理

1. 1、RAII

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

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

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

下面介绍3种智能指针,智能指针最重要的就是拷贝构造。 

1.2 auto_ptr

C++98中推出了一个auto_ptr

当初设计的不是很好,不建议使用,为什么?

因为拷贝时,管理权转移,被拷贝的对象置为空,要拷贝的对象接替了被拷贝的对象的空间

ap2拷贝ap1,ap1被置为空,ap2接受ap1原来的空间

核心思想代码就是

auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}

1.3 unique_ptr 

C++11后出现了unique_ptr

unique_ptr的实现原理:简单粗暴的防止拷贝。因此直接把拷贝与赋值禁掉了

unique_ptr构造的常用使用写法:

struct Date
{
	int _year;
	int _month;
	int _day;
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
	~Date()
	{
		cout << "~Date()" << endl;
	}
};
template<class T>
class DeleteArray
{
public:
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

class Fclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	}
};

智能指针对空间的托管  

int main()
{
	// 定制删除器
	unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
	有特化版本
	//unique_ptr<Date[]> up2(new Date[5]);
	unique_ptr<FILE, Fclose> up3(fopen("test.cpp", "r"));
    return 0;
}

其余的接口可以看文档是怎么写的 

1.4 shared_ptr

C++11推出了shared_ptr,另外shared_ptr可以进行拷贝

shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次,因此是程序不会崩溃掉。

shared_ptr的原理:

是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。例如:教室里最后一个人关灯。      

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

为什么智能指针要推出拷贝构造这个功能?就比如下面的容器存储的类型是shared_ptr就不得不进行拷贝了。

vector<shared_ptr<Date>> v;

shared_ptr构造的常用使用:

智能指针对空间的托管:

	shared_ptr<Date> sp1(new Date);
    //虽然在文档没表现但是也可以这么玩
	shared_ptr<Date[]> sp4(new Date[5]);
	shared_ptr<FILE> sp5(fopen("test.cpp", "r"), Fclose());

make_shared则是C++11引入的一个模板函数,用于更高效地创建shared_ptr实例 

std::make_shared:

用于创建std::shared_ptr的实例,并自动管理其指向的对象的生命周期。
在一次内存分配中同时创建对象和控制块,减少内存碎片和分配开销。

使用: 

shared_ptr<Date> sp6 = make_shared<Date>(2024, 8, 5);

模拟shared_ptr: 

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

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

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

		void release()
		{
			if (--(*_pcount) == 0)
			{
                //进行回调
				_del(_ptr);

				delete _pcount;
				_ptr = nullptr;
				_pcount = nullptr;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);
			}

			return *this;
		}

		~shared_ptr()
		{
			release();
		}

		T* get() const
		{
			return _ptr;
		}

		int use_count() const
		{
			return *_pcount;
		}

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

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

	private:
		T* _ptr;
		int* _pcount;
        // 不要思维固定用D _del,使用包装器也行
		function<void(T* ptr)> _del = [](T* ptr) {delete ptr; };
	};

 shared_ptr的循环引用:

假设我们要使用定义一个双向链表,如果我们想要让创建出来的链表的节点都定义成shared_ptr智能指针,那么也需要将节点内的_pre和_next的类型都转换成shared_ptr的类型。如果定义成普通指针,那么就不能赋值给shared_ptr的智能指针。

struct ListNode
{
	int _data;
	//ListNode* _next;
	//ListNode* _prev;
	shared_ptr<ListNode> _next;
	shared_ptr<ListNode> _prev;
};
int main()
{
	// 循环引用 -- 内存泄露
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);
	n1->_next = n2;
	n2->_prev = n1;
	return 0;
}

为什么会内存泄漏 ?

node1和node2析构后,两个节点引用计数减到1(node1为左节点,node2为右节点)导致内存无法释放。

循环引用分析:

1、右节点什么时候释放呢,左节点_next管着,左节点_next析构,右节点就释放了

2、左节点_next什么时候析构呢,左边节点释放,左节点_next就析构

3、左节点什么时候释放,右节点的_prev管着,右节点的_prev析构了,左边的节点就释放了

4、右节点的_prev什么时候析构呢,右节点释放,右节点_prev就析构了

最后导致左节点与右节点无法释放,发生内存泄漏

 那么如何解决?

解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了 

weak_ptr:弱指针不支持管理资源,不支持RAII

 原理:

node1->_next = node2;node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。

模拟一个简单的weak_ptr,这里的weak_ptr就是把shared_ptr的指针拿过来,没有发生计数。

严格来说是要计数的,来判断weak_ptr是否过期。

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
		{}
		// 拷贝构造
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();

			return *this;
		}

	private:
		T* _ptr = nullptr;
	};


网站公告

今日签到

点亮在社区的每一天
去签到