C++:智能指针

目录

RAII

C++标准库智能指针

auto_ptr

unique_ptr

 shared_ptr

循环引用问题

shared_ptr的线程安全问题 

weak_ptr


RAII

RAII是Resource Acquisition Is Initialization的缩写,他是一种资源管理的类的设计思想

本质是利用对象生命周期来管理获取到的动态资源,避免资源泄漏

RAII在获取资源时把资源委托给一个对象,控制着对资源的访问,资源在对象的生命周期内始终保持有效,最后在对象析构时释放资源,保证了资源的正常释放,避免资源泄露的问题

智能指针类就满足RAII的设计思路

C++标准库智能指针

C++标准库中的智能指针都在这个头文件下面

智能指针有好几种,除了weak_ptr他们都符合RAII和像指针一样的访问行为,原理上是解决智能指针拷贝时思路的不同

auto_ptr是C++98设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源管理权转给拷贝对象,这会导致他会让被拷贝对象悬空,导致访问报错的问题,强烈不建议使用auto_ptr

unique_ptr是C++11设计出来的智能指针,翻译是唯一指针,他的特点是不支持拷贝,只支持移动,不需要拷贝的场景就非常建议使用他

shared_ptr是C++11设计出来的智能指针,翻译是共享指针,他的特点是支持拷贝,也支持移动,如果需要拷贝的场景就需要使用他。底层实现方式是引用计数

weak_ptr是C++11设计出来的智能指针,翻译是弱指针,他不同于上面的智能指针,他不支持RAII,也就意味着不能用他来直接管理资源,weak_ptr的产生本质是要解决share_ptr的一个循环引用导致内存泄漏的问题

auto_ptr

template
class auto_ptr
{
public:
	auto_ptr(T* ptr)
		:_ptr(ptr)
	{}

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

	auto_ptr& operator=(auto_ptr& ap)
	{
		// 检测是否为自己给自己赋值
		if (this != &ap)
		{
			// 释放当前对象中资源
			if (_ptr)
				delete _ptr;
			// 转移ap中资源到当前对象中
			_ptr = ap._ptr;
			ap._ptr = NULL;
		}
		return *this;
	}

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

	// 像指针一样使用
	T& operator*()
	{
		return *_ptr;
	}

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

unique_ptr

template
class unique_ptr
{
public:
	explicit unique_ptr(T* ptr)
		:_ptr(ptr)
	{}

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

	// 像指针⼀样使⽤
	T& operator*()
	{
		return *_ptr;
	}

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

	unique_ptr(const unique_ptr& sp) = delete;

	unique_ptr& operator=(const unique_ptr& sp) = delete;

	unique_ptr(unique_ptr&& sp)
		:_ptr(sp._ptr)
	{
		sp._ptr = nullptr;
	}

	unique_ptr& operator=(unique_ptr&& sp)
	{
		delete _ptr;
		_ptr = sp._ptr;
		sp._ptr = nullptr;
	}
private:
	T* _ptr;
};

 shared_ptr

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

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

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

	void release()
	{
		if (--(*_pcount) == 0)
		{
			_del(_ptr);
			delete _pcount;
			_ptr = _pcount = nullptr;
		}
	}

	shared_ptr& operator=(const shared_ptr& sp)
	{
		if (_ptr != sp._ptr)
		{
			release();
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			_del = sp._del;
			(*_pcount)++;
		}
	}

	~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;
	function _del = [](T* ptr) {delete ptr; }; // 删除器
};

shared_ptr管理一份资源就需要一份引用计数,所有引用计数用静态成员的方式是无法解决的,而是采用了堆上动态开辟的方式

当构造智能指针对象时来一份资源就要new一个引用计数,多个shared_ptr指向资源时就++引用计数即可,当shared_ptr析构时只需要--引用计数,当引用计数--到0后代表当前析构的shared_ptr是最后一个管理资源的对象,则析构资源

C++:智能指针_第1张图片

循环引用问题

若有如下代码

struct ListNode
{
	int data;
	std::shared_ptr _next;
	std::shared_ptr _prev;
};

int main()
{
	std::shared_ptr n1(new ListNode);
	std::shared_ptr n2(new ListNode);
	n1->next = n2;
	n2->prev = n1;

	return 0;
}

C++:智能指针_第2张图片 

n1和n2析构后,管理两个节点的引用计数减到1

右边的节点释放需要左边的next不再管理,next析构后右边的节点就可以释放

next是左边的节点,而左边的节点释放需要右边的prev不再管理,prev释放后next才可以释放

prev是右边的节点,右边的节点需要右边的节点释放,至此逻辑上就形成了循环引用,谁都不会释放,导致内存泄漏

解决这个问题只需要把ListNode结构体中的next和prev改成weak_ptr,weak_ptr绑定到shared_ptr时不会增加它的引用计数,next和prev不再参与资源释放管理逻辑,就成功的打破了引用循环

struct ListNode
{
	int data;
	std::weak_ptr _next;
	std::weak_ptr _prev;
};

shared_ptr的线程安全问题 

shared_ptr的引用计数在堆上,如果多个shared_ptr对象在多个线程中进行拷贝析构的操作修改引用计数,就会存在线程安全问题,所以shared_ptr引用计数是需要加锁或者原子操作保证线程安全的

我们可以在shared_ptr引用计数从int*改成atomic就可以保证引用计数的线程安全问题,或者使用互斥锁加锁也可以

private:
	T* _ptr;
	atomic _pcount;
	function _del = [](T* ptr) {delete ptr; }; // 删除器
};

weak_ptr

template
class weak_ptr
{
public:
	weak_ptr()
	{}

	weak_ptr(const shared_ptr& sp)
		:_ptr(sp.get())
	{}

	weak_ptr& operator=(const shared_ptr& sp)
	{
		_ptr = sp.get();
		return *this;
	}
private:
	T * _ptr = nullptr;
};

weak_ptr不支持RAII,不支持访问资源,只支持绑定到shared_ptr,且不增加shared_ptr的引用计数,用来解决shared_ptr循环引用的问题

weak_ptr没有重载operator*和operator->等,因为它不参与资源管理,如果它绑定的shared_ptr已经释放了资源,weak_ptr再去访问资源是很危险的


完 

你可能感兴趣的:(C++语法与数据结构,c++,开发语言)