shared_ptr循环引用问题与仿函数实现定制删除器

一    shared_ptr的循环引用问题

一:智能指针发展历史

智能指针发展历史:

二:shared_ptr中的循环引用问题
我们前面博客都已经了解了shared_ptr是采用引用计数的方式进行,但是它存在一个问题,就是循环引用,通过底下例子我们来详细看看。
struct ListNode
{
	int _data;
	ListNode* _next;
	ListNode* _prev;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
void Test()
{
	ListNode* cur = new ListNode;
	ListNode* next = new ListNode;
	cur->_next = next;//如果此处抛异常便不能释放  return throw
	next->_prev = cur;
	delete cur;
	delete next;
}


以上代码,就是两个链表结点,连起来,然后释放。但是我们会发现如果再delete之前return,那就会出现内存泄漏,因为底下的delete根本没有机会执行。

于是我们就想到用智能指针来管理,(RAII)析构时自己释放(如果不懂,就去参考上篇智能指针的博客)

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<memory>
#include<boost/smart_ptr.hpp>
using namespace std;
struct ListNode
{
	int _data;
	std::shared_ptr<ListNode> _prev;
	std::shared_ptr<ListNode> _next;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
void test_smart_ptr()
{
	std::shared_ptr<ListNode>cur(new ListNode);//有内存泄漏
	std::shared_ptr<ListNode>next(new ListNode);
	//下面两句导致循环引用
	//cur->_next = next;
	//next->_prev = cur;
}

什么原因导致的呢?

循环引用,我们都知道,出了作用域,cur和next都被释放了,两个结点的引用计数都减为1,因为分别有智能指针_prev和智能指针_next指向这两个结点,但是你就会发下如果

想要把第一块空间释放,就必须把第二块空间_prev先释放,第二块空间想要释放,就要把第一块空间的_next释放,由于两块空间的释放都限制于对方,所以就都释放不了,造成内存泄漏。

三  如何解决循环引用问题(weak_ptr弱指针)

为了解决循环引用的问题我们加入了弱指针weak_ptr来辅助shared_ptr。注意weak_ptr不能单独使用,必须辅助shared_ptr才能使用。weak_ptr是一种不控制所指向对象生存周期的智能指针,它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被销毁,即使有weak_ptr指向对象,对象还是会被释放。

因此上面代码可改为。

struct ListNode
{
	int _data;
	std::weak_ptr<ListNode> _prev;
	std::weak_ptr<ListNode> _next;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
void test_smart_ptr()
{
	std::shared_ptr<ListNode>cur(new ListNode);//有内存泄漏
	std::shared_ptr<ListNode>next(new ListNode);
	cout << cur.use_count ()<< endl;
	cout << next.use_count() << endl;
	//下面两句导致循环引用
	cur->_next = next;
	next->_prev = cur;
	cout << cur.use_count() << endl;
	cout << next.use_count() << endl;
}

看下weak_ptr的实现

template<class T>
class  Weakptr;
template<class T>
class Sharedptr
{   
	friend class Weakptr<T>;//声明成友元,这样就可以在Weakptr中访问Sharedptr的私有成员
public:
	Sharedptr(T* ptr = NULL)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}
	~Sharedptr()
	{
		if (--*(_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
			_ptr = NULL;
			_pcount = NULL;
		}
	}
	Sharedptr(const Sharedptr<T> &sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		++(*_pcount);
	}
	Sharedptr<T>& operator=(Sharedptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			(*_pcount)++;
		}
		return *this;
	}
	int Use_count()
	{
		return *_pcount;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int*_pcount;//不能为int count 原因是:如果为int count,那就说明每个对象有各自count,如果是指针,多个对象指向同一块空间  
};
template<class T>
class  Weakptr//注意Weakptr不能单独存在,她辅助Sharedptr
{  
public:
	Weakptr()
	:_ptr(NULL)
	{}
	Weakptr(Sharedptr<T>& sp)
	{
		_ptr = sp._ptr;
	}
	Weakptr<T>& operator= (Sharedptr<T>& sp)
	{
		_ptr = sp._ptr;
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
private:
	T* _ptr;
};
struct ListNode
{
	int _data;
	Weakptr<ListNode> _prev;//没有缺省构造的类成员变量,必须在初始化列表初始化。因此必须给个Sharedptr缺省构造
	Weakptr<ListNode> _next;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
void testSharedptr()
{
	Sharedptr<ListNode>cur(new ListNode);//有内存泄漏
	Sharedptr<ListNode>next(new ListNode);
	cout << cur.Use_count() << endl;
	cout << next.Use_count() << endl;
	//下面两句导致循环引用
	cur->_next = next;
	next->_prev = cur;
	cout << cur.Use_count() << endl;
	cout << next.Use_count() << endl;
}
int main()
{
	testSharedptr();
	system("pause");
	return 0;
}


二  利用仿函数实现定制删除器

一   首先我们要了解什么是仿函数

仿函数:就是使一个类的使用看上去像一个函数,仿函数就是一个空类,里面重载了operator(),这样这个函数就有了类似函数的行为,就是一个仿函数类了。

class Less
{
public:
	bool  operator()(const int&l, const int &r)
	{
		return 1 < r;
	}
};
int main()
{
	int s1 = 2;
	int s2 = 3;
	Less less;
	cout << less(s1, s2) << endl;//less.operator(s1,s2);
	system("pause");
	return 0;
}

二   简单的利用仿函数实现定制删除器

在这里我们需要对一个智能指针的问题探究,当我们进行资源处理时。无非是两种资源,一种是文件,一种是堆内存。对于文件我们fopen,然后fclose,对于堆内存,我们可能会new new[] malloc,当我们释放的时候,当然对应着delete,delete[],free。所以我们使用智能指针的时候,需要让它对删除器进行操作,比如我们打开一个文件能够让它识别,最后关闭文件。


int main()
{
	std::shared_ptr<FILE>sp1(fopen("haha.txt", "w"));
}

如图上面的代码,智能指针默认的是管理内存new[],delete[],而上面是个文件资源因此报错。

class Fclose
{
public:
	void operator()(FILE* f)
	{
		fclose(f);
		cout << "FLCLOSE" << endl;
	}
};
void test()
{
	Fclose fc;
	std::shared_ptr<File>sp1(fopen("haha.txt", "w"),fc);//将Fclose对象传递过去,文件正常关闭
	
}
int main()
{
	test();
	system("pause");
	return 0;
}

再举一个例子,用智能指针malloc开辟的的动态内存,我们在释放的时候就要用free释放。

//定制删除器的仿函数
class Free
{
public:
	void operator()(void* ptr)
	{
		free(ptr);
		cout << "Free" << endl;
	}
};
void test()
{
	Free fr;
	std::shared_ptr<int>sp1((int *)malloc(sizeof(int)),fr);//将Fclose对象传递过去,文件正常关闭
	
}
int main()
{
	test();
	system("pause");
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值