C++:智能指针,这就是你想要的

前言:

智能指针主要用来释放资源,防止某些情况下导致内存泄漏

当我们使用new关键字在堆上申请了一块空间,而没有进行delete,这就造成了内存泄漏;还有如果fopen打开文件后,没有关闭。这也造成了隐患。

当然上面这一些明显的错误,还有一些难以察觉的错误。

int*p=  new int (0);

fun();

delete p;

//在执行fun函数的时候,发生异常情况,直接退出,而此时申请的资源还没有释放,而且不会再执行到下面的delete p这个语句,也就造成了内存泄漏

 

 而通过Effecitive C++,我们可以了解到以对象管理系统资源,其实也就是用到了我们今天的智能指针,利用 对象生命资源周期 来控制资源的释放 即RAII

智能指针 

智能指针就是一个基于RAII思想的一个管理资源的类,把需要释放的资源放到类对象中,在类对象生命周期结束时,自动释放资源

 基于上面的思想,我们可以自己设计一个简单的类来初步实现上面的想法

 

template 
class smart_pointer{
public:
	smart_pointer( T* ptr)
		:_ptr(ptr)
	{}
	~smart_pointer()
	{
		std::cout << "资源释放完毕" << std::endl;
		delete _ptr;
		_ptr = nullptr;
	}
	T& operator* ()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

 我们可以将资源放到我们写的smartpointer类对象中,不需要我们手动释放资源;当smartpointer对象出作用域后,自动释放资源,并且能够通过类对象访问资源;但是还存在一个致命问题:倘若一份资源保存到两个smartpointer类中,就会将同一个资源析构两次即出现错误double free

 为了避免出现上面的情况,C++实现了智能指针auto_ptr、unique_ptr、shared_ptr;我们下面也将就这三个只能指针就行了解

auto_ptr: 

特点:转移对资源的管理权 

问题一:

 若通过拷贝构造函数或赋值拷贝操作符复制auto_ptr,他们就会变成nullptr,而复制所得的指针将取得资源的唯一拥有权!

	int* p = new int(7);     //资源p
	auto_ptr ap1(p);    //ap1管理资源p
	auto_ptr ap2(ap1);  //ap2管理资源p,ap1为nullptr
	auto_ptr ap3 = ap2;  //ap3管理资源p,ap2为nullptr

 如果有疑惑,可参考一下实现这种功能的下列代码

 

	template 
	class auto_ptr{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~auto_ptr()
		{
			std::cout << "资源释放完毕" << std::endl;
			if (_ptr!=nullptr)
			delete _ptr;

			_ptr = nullptr;
		}
		auto_ptr(auto_ptr& autoptr)
		{
			_ptr = autoptr._ptr;
			autoptr._ptr = nullptr;
		}
		auto_ptr& operator=(auto_ptr& autoptr)
		{
			if (_ptr != autoptr._ptr)
			{
				if (_ptr!=nullptr)
				delete _ptr;

				_ptr = autoptr._ptr;
				autoptr._ptr = nullptr;
			}
			return *this;
		}
		T& operator* ()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

 

 问题二:

	int* p = new int(7);
	
	auto_ptr ap1(p);
	auto_ptr ap2(p);

将资源p同时用两个auto_ptr管理,这也会导致刚才我们所说的同一资源析构两次,而对于这种情况而言,库函数没有相应的措施应对。

所以,我们在使用智能指针的时候,一定要注意这个问题:

别让多个智能指针同时指向同一对象。如果多个指向同一对象,会被删除一次以上,导致不确定的行为,程序会崩溃

 

对于问题一的解决方法,还有其余智能指针

unique_ptr 

特点:禁止使用赋值拷贝和赋值运算符函数

即在代码中使用C++11的delete

unique_ptr(unique_ptr& up)=delete;

unique_ptr& operator=(unique_ptr& up) = delete;

shared_ptr 

特点:引用计数型指针(RCSP) ,

对管理资源的智能指针进行计数,在无智能指针管理这个资源时会自动删除该资源。

 

实现原理 

	template 
	class shared_ptr{
		void add_count()
		{
			_pmutex->lock();
			++(*_pcount);
			_pmutex->unlock();
		}
		void release()
		{
			bool flag = false;
			_pmutex->lock();
			--(*_pcount);
			if ((*_pcount) == 0)
			{
				if (_ptr!=nullptr)
				delete _ptr;
				if (_pcount!=nullptr)
				delete _pcount;
				_ptr = nullptr;
				_pcount = nullptr;
				flag = true;
			}
			_pmutex->unlock();

			if (flag == true)
			{
				delete _pmutex;
				_pmutex = nullptr;
			}
		}
	public:
		shared_ptr(T* ptr=nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _pmutex(new mutex)
		{}
		~shared_ptr()
		{
			release();
		}
		shared_ptr(shared_ptr& sharedptr)
		{
			_ptr = sharedptr._ptr;
			_pcount = sharedptr._pcount;
			_pmutex = sharedptr._pmutex;
			add_count();
		}
		shared_ptr& operator=(shared_ptr& sharedptr)
		{
			if (_ptr != sharedptr._ptr)
			{
				release(); //将原来管理的资源的引用计数减1;
				_ptr = sharedptr._ptr;
				_pcount = sharedptr._pcount;
				_pmutex = sharedptr._pmutex;
				add_count();//将现在管理的资源的引用计数加1;
			}
			return *this;
		}
		T* get_ptr() const
		{
			return _ptr;
		}
		T& operator* ()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T*    _ptr;
		int*  _pcount;
		std::mutex *   _pmutex;
	};

由shared_ptr的实现原理,我们发现它是线程安全的,但是shared_ptr还存在一个魔咒,那就是环形引用;

而想解决环形引用这个问题,就需要提到weak_ptr

weak_ptr实现原理:

	template 
	class weak_ptr{
		weak_ptr() = default;
		weak_ptr(const shared_ptr& p)
		{
			_ptr = p.get_ptr();
		}
		weak_ptr& oprator = (const shared_ptr& p)
		{
			_ptr = p.get_ptr();
			return *this;
		}
		T& operator* ()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}


	private:
		T* _ptr;
	};

就上面的环形引用,我们现在看一下这个场景

struct ListNode{

	int data;
   
	smartpointer::shared_ptr _prev;//smartpointer::weak_ptr _prev;
	smartpointer::shared_ptr _next;//smartpointer::weak_ptr _next;

	~ListNode()
	{
		cout << "析构" << endl;
	}
};

int main()
{
	smartpointer::shared_ptr p1(new ListNode);
	smartpointer::shared_ptr p2(new ListNode);

	p1->_prev = p2;
	p2->_next = p1;
	
	return 0;
}

 相当于一个链表不过是一个环形的,而在这种情况下,shared_ptr就不能很好地完成释放资源的任务,原因就在于shared_ptr的特性,当引用计数为0时,自动释放资源,而在环形引用的情况下,计数引用不可能为0;

而weak_ptr可以解决这种环形引用的情况,是因为weak_ptr实际是封装了shared_ptr,不会使shared_ptr的计数器加一.

 

C++:智能指针,这就是你想要的_第1张图片

通过观察上面的实现代码,我们发现一点构造函数调用new,析构函数也调用delete;如果我们使用new[] 申请资源,并用上面的智能指针管理,程序会崩溃! 那我们对new[]这种方法申请的资源怎么办呢?还是想以前一样的老方法嘛!

其实不急!智能指针也可以解决,智能你给一个定制的删除器,就是自己写一个对应释放资源的仿函数,传入智能指针即可

template
class Delete{
public:
	void operator()(T* p)
	{
		delete [] p;
	}
};
int* p = new int[7];
std::shared_ptr p1(p, Delete());

 最后结合Effective C++探讨一下管理资源的问题:

  1. 我们要使用对象管理资源,如智能指针
  2. 为了防止内存泄漏,我们申请到资源,就立即放到智能指针中进行管理;防止因异常等情况的造成的内存泄漏发生
  3. new和delete、new [] 和delete [] 要成对使用,在使用智能指针时,我们要判断是否要传入定制删除器
  4. 以独立语句将new对象放到智能指针对象中

 以独立语句将new对象放到智能指针对象的理解:

int priority();

void processWidget(std::shared_ptr (new Widget) , int priority )//Widget是一个类

在调用processWidget之前,编译器必须创建代码,做以下三种事情:

  1. 调用priority 函数
  2. 执行new Widget
  3. 调用 shared_ptr构造函数

C++编译器以什么样的次序完成这件事情呢?不确定

可以确定的是 new Widget 一定执行于tr1::shared_ptr构造函数被调用之前,因为这个表达式的结果还要被传递作为shared_ptr构造函数的一个实参,但对priority的调用可以排在第一、第二或第三。

如果第二位执行:

执行new Widget

调用priority

调用 shared_ptr构造函数

调用priority异常,极有可能造成资源泄漏。在此情况下 new Widget 的返回指针遗失,尚未放入shared_ptr(我们用来防止资源泄漏的武器)

所以这就有了我们的忠告 :以独立语句将new对象放到智能指针对象

即:

shared_ptr pw(new Widget)

void processWidget(shared_ptr pw , int priority ) 

 

 

 

 

各位小伙伴,本片博文到这里就结束了,不知道你有没有看尽兴呢!

好了,不管怎么样,谢谢大家观看书生频道,欢迎大家留言!

如果本片博文有错误的地方,欢迎大家评论区给与指正! 

你可能感兴趣的:(C/C++)