前言

我们都知道使用了new申请了堆区空间之后都需要手动释放资源。这样的情况在编程过程中十分的常见,甚至有些时候由于一些异常等原因,我们的程序无法完成我们的释放工作,从而导致内存泄露。此时有人提出了RAII的思想,我们C++11正式推出智能指针的语法。本质就是利用RAII

本文章小编会带大家了解RAII和C++智能指针的用法和模拟实现

1. RAII

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

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

这种做法有两大好处:

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

例1:

//正确包含头文件
class A
{
public:
	A()
	{
		if (nullptr == _ptr)
		{
			_ptr = new int;
			cout << "申请成功" << endl;
		}
	}
	~A()
	{
		if (nullptr != _ptr)
		{
			delete _ptr;
			cout << "释放成功" << endl;
		}
	}

private:
	int* _ptr;
};

int main()
{
	cout << "程序开始" << endl;
	
	{
		//局部域
		A a;
	}

	cout << "程序结束" << endl;

	return 0;
}

上面代码中,我们的A类中包含一个new的整型指针,在创建A类对象的时候对指针进行初始化,而当局部域结束过后,该对象自动调用析构函数,从而释放资源。、

所以,我们可以利用上面例1的这样的形式,可以将一个指针交给一个类来管理。这便是智能指针的雏形。

2. C++智能指针和Boost库

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptrshared_ptrweak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptrshared_ptrweak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

3. 智能指针

  • 头文件:memory

  • 需求:

    1. 被类包装了
    2. 具有指针的操作:*, ->, =……(常见的)

所以很明显:运算符重载就发挥了极大的作用!!!

  • 注意:这里需要特别强调的是关于指针的赋值操作!!

3.1 auto_ptr

  • 文档:auto_ptr

  • auto_ptr:C++98版本的库中就提供了auto_ptr的智能指针。

  • 特点

    1. auto_ptr赋值操作,会将指针的管理权转移。即,一旦auto_ptr发生了拷贝赋值操作,那么原来的指针变量不再起作用了……造成了被拷贝对象悬空的问题。

例2:

#include <memory>

int main()
{
	auto_ptr<int> p1(new int(1));
	auto_ptr<int> p2;
	
	p2 = p1;
	//++(*p1); //非常危险的代码
	++(*p2);

	return 0;
}
  • 监视窗口观察:
    在这里插入图片描述

所以上面代码中的:++(*p1)是十分危险的。

所以auto_ptr的局限性非常强,不适合日常使用!

3.1.1 auto_ptr实现

namespace Er
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			: _ptr(ptr)
		{}
		auto_ptr()
			:_ptr(nullptr)
		{}

		auto_ptr(auto_ptr<T>& ap)
			: _ptr(ap._ptr)
		{
			ap._ptr = nullptr; //赋值的置为空
		}
		
		const auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if(this != ap)
			{
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;	
		}

		~auto_ptr()
		{
			delete _ptr;
		}

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

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

	private:
		T* _ptr;
	};
}

3.2 unique_ptr

  • 文档:unique_ptr

  • 特点

    1. 直接防止赋值和拷贝。仅允许一个指针存在。

例3:
在这里插入图片描述

3.2.1 unique_ptr实现

namespace Er
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			: _ptr(ptr)
		{}
	
		//方法二:接后缀delete --> 代表删除
		unique_ptr(unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
	
	
		~unique_ptr()
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
		}
	
		T& operator*()
		{
			return *_ptr;
		}
	
		T* operator->()
		{
			return _ptr;
		}
	
	private:
		//方法一: 只声明不实现 -- 放在私有
		//unique_ptr(unique_ptr<T>& up);
		//unique_ptr<T>& operator=(const unique_ptr<T>& up);
		T* _ptr;
	};
}

3.3 shared_ptr

  • 文档:shared_ptr

  • 特点

    几乎和指针的行为一致。支持赋值和拷贝,且能正常释放资源

  • 问题循环引用

例5:

#include <memory>

int main()
{
	shared_ptr<int> p1(new int);
	shared_ptr<int> p2(new int);

	p2 = p1;
	shared_ptr<int> p3(p1);

	return 0;
}

3.3.1 shared_ptr的简单实现

为什么说简单?因为我们需要解决多线程的竞争问题。

  • 问题一:我们都知道,指针指向的资源只有一份。多个指针指向同一份资源,如何做到成功释放资源的呢?

    举个例子,现在我们一个班有50个学生,但是灯光资源只有一份。所以当我们所有人都离开教室的时候,这个灯光我们才关闭。

    简要地说:最后一个离开的释放资源

    这就是计数引用的思想。

  • 问题二:我们如何让多个指针对象看到同一份计数资源呢?

    采用堆区的资源

    但是我们不能采用静态成员变量的形式。静态成员变量是属于类的,但是我们要求这个计数引用对于指向资源的数量统计!!!

namespace Er
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pcount(new int(1))
		{}
	
		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}
	
		//赋值运算符需要考虑的场景:
		// 1、sp3 = sp1 指向同一个位置
		// 2、sp4 = sp4 刚好创建,自己给自己赋值
		// 3、sp2 = sp1 另一个指向的指针,指向同一空间
		//   --> 刚好是最后一个/不是
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr == sp._ptr) //防止自我赋值
				return *this; //指向同一片空间就停止赋值
	
			if (--(*_pcount) == 0) //如果自己是最后一个
			{
				delete _ptr;
				delete _pcount;
			}
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++(*_pcount);
	
			return *this;
		}
	
		~shared_ptr()
		{
			if (--(*_pcount) == 0) //保证最后一个对象析构
			{
				delete _ptr;
				delete _pcount;
			}
		}
	
		T* get() const 
		{
			return _ptr;
		}
	
		T& operator*()
		{
			return *_ptr;
		}
	
		T* operator->()
		{
			return _ptr;
		}
	
	private:
		T* _ptr;
		int* _pcount;
	
		//不可行方案:
		//int _count;
		//static int _count;
	};
}

3.3.2 循环计数和weak_ptr

来看下面的场景:

例6:

struct Node
{
	int a;
	shared_ptr<Node> _prev;
	shared_ptr<Node> _next;
}

int main()
{
	shared_ptr<Node> n1(new Node);
	shared_ptr<Node> n2(new Node);
	n1->_next = n2;
	n2->_prev = n1;
}
  • 上面代码有什么问题?

    两个节点的资源都无法释放!!!下面给出图解
    在这里插入图片描述
    这就是循环引用的问题

  • weak_ptr就是专门用于解决shared_ptr的循环引用的问题的,因为它不增加计数引用!

    所以它没有自己的专门的构造。
    文档:weak_ptr

  • 不过注意:weak_ptr不是RAII

例7:

struct Node
{
	int a;
	weak_ptr<Node> _prev;
	weak_ptr<Node> _next;
}

int main()
{
	shared_ptr<Node> n1(new Node);
	shared_ptr<Node> n2(new Node);
	n1->_next = n2;
	n2->_prev = n1;
}
  • 模拟实现:
namespace Er
{
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}

		weak_ptr(const weak_ptr<T>& wp)
			: _ptr(wp._ptr)
		{}

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

		~weak_ptr()
		{}

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

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

	private:
		T* _ptr;
	};
}

3.3.3 shared_ptr的定制删除器

我们来看shared_ptr的文档:

在这里插入图片描述

这是为什么呢?

来看这样的场景:

//面对这三种情况如何应对呢?
shared_ptr<A> sp1(new A[4]);
shared_ptr<A> sp2((A*)malloc(sizeof(A)));
shared_ptr<FILE> sp3(fopen("data.txt","r"));

所以我们必须把自己的释放资源的方法传入。

如果我们需要完善我们的自行的shared_ptr,我们必须想办法能够有参数接受各种删除方法。

可喜的是:这些删除办法都是可执行对象。并且返回值和参数类型都是:void(void)我们可以使用包装器来完成,定制删除器。

例:

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

		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pcount(new int(1))
		{}

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

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr == sp._ptr)
				return *this; //指向同一片空间就停止赋值

			if (--(*_pcount) == 0)
			{
				cout << "ptr析构成功" << endl;
				_del(_ptr);
				delete _pcount;
			}
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			_del = sp._del;
			++(*_pcount);

			return *this;
		}


		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				cout << "ptr析构成功" << endl;
				_del(_ptr); //调用删除器
				delete _pcount;
			}
		}

		T* get() const
		{
			return _ptr;
		}

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

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

	private:
		T* _ptr;
		int* _pcount;
		function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};
}

完。

希望这篇文章能够帮助你!!!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐