C++中的RAII

在C++中,如果手动分配和释放内存,很容易会引起内存泄漏的问题,第一是代码必须显示的写出资源分配和释放,很容易忘记写释放这部分,第二是如果在分配内存之后产生异常,无法执行释放内存的代码。

针对第二点,也可以通过try catch的方式,如果执行产生异常,那就在catch里去释放,但这样对分配单个资源有效,如果是分配多个资源,在catch里释放时就要判断每个资源是否分配成功,比较麻烦,另外如果在某个函数调用里去分配资源,在分配资源的时候分配失败了,回到了上层函数,那么就无法正常释放资源(在函数里定义的指针和分配的资源都是局部对象)。

通过RAII的思想就可以解决上述问题,RAII简单来说就是资源获取即为初始化,也就是在将资源绑定到一个对象上(智能指针就是这个对象,不过是一个模板类,而且重载了*和->运算符,可以像指针一样使用的对象),在该对象析构函数中释放资源,因此出对象作用域时,对象自动析构,也就自动将资源释放了。

因此,就有了最简单的智能指针的定义,首先它是一个模板类,里面有一个类的指针,然后需要重载*和->运算符,返回引用和指针。类似这样:

template <class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr() { cout << "delete:" << _ptr << endl; delete _ptr; _ptr = nullptr; }

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

private:
	T* _ptr;
};

但这样带来的一个明显的问题是,在拷贝构造或者赋值时,两个智能指针会指向同一块地址,这样就会造成多次析构,造成程序崩溃。对这个问题,C++98中的auto_ptr(C++98中唯一的智能指针)的解决方法是将所有权转移,也就是拷贝或赋值时将原来的指针置空,但是这样又带来了新的问题,就是原来的指针不能用了,因此auto_ptr是一个比较失败的设计。它的实现如下:

template <class T>
class auto_ptr
{
public:
	auto_ptr(T* ptr)
		:_ptr(ptr)
	{}
	auto_ptr(auto_ptr<T>& p)
	{
		_ptr = p._ptr;
		p._ptr = nullptr;
	}
	auto_ptr<T>& operator=(auto_ptr<T>& p)
	{
		_ptr = p._ptr;
		p._ptr = nullptr;
	}

	~auto_ptr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}


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

private:
	T* _ptr;
};

后来C++中的unique_ptr提供了另一种思路,即禁止拷贝和复制,这个智能指针独占一种资源,它的模拟实现如下:

template <class T>
class unique_ptr
{
public:
	unique_ptr(T* ptr)
		:_ptr(ptr)
	{}
	unique_ptr(const unique_ptr<T>& p) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>& p) = delete;

	~unique_ptr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
		_ptr = nullptr;
	}


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

private:
	T* _ptr;
};

拷贝构造函数和赋值的操作符重载函数后加上了delete,表示不能调用该函数,并且不能定义该函数。

那么unique_ptr就比较死板,如果想拷贝,想赋值呢?shared_ptr来了,它通过计数的方式,每次拷贝或赋值计数加一,每次析构计数减一,只有减到0的时候才去释放资源,由于计数也必须在拷贝或赋值时传递,因此将计数以指针的形式传递,只有在通过类构造时去new一个计数,而在拷贝或赋值时均传递计数的指针,在计数为0释放资源时,同步释放计数的资源。它的模拟实现如下:

template <class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr)
		: _ptr(ptr)
		, _pCount(new int(1))
	{}
	shared_ptr(const shared_ptr<T>& p)
	{
		_ptr = p._ptr;
		_pCount = p._pCount;
		(*_pCount)++;
	}
	void Release()	
	{
		if (--(*_pCount) == 0) // 当计数为0,需要释放pCount
		{
			if (_ptr) // 如果_ptr为空,只要释放pCount
			{
				delete _ptr;
				_ptr = nullptr;
			}
			cout << "~shared_ptr()" << endl;
			delete _pCount;
			_pCount = nullptr;
		}
	}

	shared_ptr<T>& operator=(const shared_ptr<T>& p)
	{
		// 指向资源不同时,使两指针指向同一资源,并且计数增加
		if (_ptr != p._ptr) // 当指向资源相同时,没有必要进行赋值
		{
			Release(); // 先释放该指针之前指向的空间

			_ptr = p._ptr;
			_pCount = p._pCount;
			* _pCount++;
		}
	}

	~shared_ptr()
	{
		Release();
	}

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

private:
	T* _ptr;
	int* _pCount;
};

但是shared_ptr仍然不完美,因为循环引用时会带来问题,典型的例子是双向链表,双向链表的节点包含之前前后两个节点的指针,如果都用智能指针来表示。定义两个双向链表类的对象p1和p2,都用智能指针来表示,如果仅定义,那么用sharedptr没有问题,但是如果将p2赋值给p1的后继指针,那么p2的count将加1,变为2,再将p1赋值给p2的前指针,那么p1的count也将加1,变为2,此时结束,两个对象的count都会减1变成1,但是不会触发释放内存。

那么如何解决这个问题呢,如果把链表节点类里的前后指针只用于指向,而不增加或减小计数就可以了,这个就是weak_ptr,它的模拟实现如下:

template <class T>
class weak_ptr
{
public:
	weak_ptr(T* ptr)
		:_ptr(ptr)
	{}
	weak_ptr(weak_ptr<T>& p)
	{
		_ptr = p._ptr;
	}
	weak_ptr<T>& operator=(weak_ptr<T>& p)
	{
		_ptr = p._ptr;
		return *this;
	}
	weak_ptr<T>& operator=(shared_ptr<T>& p)
	{
		_ptr = p.get();
		return *this;
	}

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

private:
	T* _ptr;
};

// Node的前后指针类型改为weak_ptr	
struct Node
{
	Node()
		: _prev(nullptr)
		, _next(nullptr)
		, _val(0)
	{}
	myPtr::weak_ptr<Node> _prev;
	myPtr::weak_ptr<Node> _next;
	int _val;
};

weak_ptr作为弱指针,即不参与资源的管理,只是用来指向资源,类似于容器里的迭代器。

另一个问题又来了,上述实现中都是使用的delete,但是如果资源分配时使用的是malloc,或者是打开的一个文件,资源释放时就不能用delete了,而是需要定制删除方法,所谓定制化删除器,在C++中其实现原理也很简单,是通过一个模板类仿函数,作为模板参数被传入。unique_ptr的定制化删除器可以实现如下:

template <class T>
struct DefaultDelete
{
	void operator()(T* p)
	{
		delete p;
		cout << "delete:" << p << endl;
	}
};

template <class T, class Delete = DefaultDelete<T>>
class unique_ptr
{
public:
	unique_ptr(T* ptr)
		:_ptr(ptr)
	{}
	unique_ptr(const unique_ptr<T>& p) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>& p) = delete;

	~unique_ptr()
	{
		Delete del;
		//cout << "delete:" << _ptr << endl;
		del(_ptr);
		_ptr = nullptr;
	}


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

private:
	T* _ptr;
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值