C++智能指针

智能指针

    • RAII
    • 智能指针
      • ①auto_ptr
      • ②unique_ptr
      • ③share_ptr
        • 循环引用
      • ④weak_ptr
    • 定制删除器

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的简单技术。

对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源

我们实际上把管理一份资源的责任托付给了一个对象。这种做法有两大好处:
1.不需要显式地释放资源
2.采用这种方式,对象所需的资源在其生命期内始终保持有效

典型的应用有智能指针和lock_guard

智能指针

智能指针除了具有RAII特性外,还需要像指针一样去使用它,所以还需要重载operator*与->

#include
using namespace std;
#include
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{
		cout << "构造函数获取资源" << endl;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~SmartPtr()
	{
		delete _ptr;
		cout << "析构函数释放资源" << endl;
	}
private:
	T* _ptr;
};
int main()
{
	SmartPtr<string> sp(new string("hello world"));//将资源托管给sp
	cout << sp->c_str() << endl;//sp->->c_str()
	cout << *sp << endl;
	return 0;
}

C++智能指针_第1张图片
总结一下智能指针的原理:
1. RAII特性
2. 重载operator*和opertaor->,具有像指针一样的行为

此时智能指针如果调用默认生成的拷贝构造会发生浅拷贝,会释放两次资源
如果使用深拷贝就失去指针拷贝的意义了,必须要让指针指向同一块空间

所以如何解决这个拷贝问题就诞生除了不同类型的智能指针

①auto_ptr

C++98推出的auto_ptr解决拷贝问题是通过管理权的交接,每拷贝一次管理权就交接一次,让最后一个拷贝的智能指针释放资源

#include
using namespace std;
#include
namespace tzc
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{
			cout << "构造函数获取资源" << endl;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		//管理权交接
		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;//交接后,自己就不参与管理了
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)
			{
				delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = nullptr;//管理权转移
			}
			return *this;
		}
		~auto_ptr()
		{
			if (_ptr != nullptr)
			{
				delete _ptr;
				cout << "析构函数释放资源" << endl;
			}
		}
	public:
		T* _ptr;
	};
};

int main()
{
	tzc::auto_ptr<string> sp1(new string("hello world"));//将资源托管给sp
	tzc::auto_ptr<string> sp2(sp1);
	cout << sp2->c_str() << endl;
	//此时sp1==nullptr,并不能解引用
	if (sp1._ptr == nullptr)
	{
		cout << "sp1==nullptr" << endl;
	}
	return 0;
}

C++智能指针_第2张图片
所以auto_ptr导致原来的智能指针不能访问资源,并不能够很好地解决问题,不推荐使用
C++11后为了解决该问题推出了其他的智能指针

②unique_ptr

unique_ptr是通过简单,粗暴地禁止拷贝来解决

namespace tzc
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{
			cout << "构造函数获取资源" << endl;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		unique_ptr(const unique_ptr<T>&)=delete ;
		unique_ptr<T>& operator=(unique_ptr<T>&) = delete;
		~unique_ptr()
		{
			if (_ptr != nullptr)
			{
				delete _ptr;
				cout << "析构函数释放资源" << endl;
			}
		}
	public:
		T* _ptr;
	};
};
int main()
{
	tzc::unique_ptr<string> sp1(new string("hello world"));//将资源托管给sp
	tzc::unique_ptr<string> sp2(sp1);//禁止拷贝
	cout << sp1->c_str() << endl;
	
	return 0;
}

③share_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源

具体解决拷贝问题:
1.shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享
2. 在创建对象是把引用计数初始化为1,在对象被销毁时,对象的引用计数减一,拷贝或者赋值时计数加一
3. 当引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源,否则就不用释放

引用计数必须让该对象的拷贝或赋值的对象能够拿到同一份计数:
如果使用静态成员变量,在模板中,同一个类共用一份计数,还是无法解决,而通过维护一个在堆上开辟空间的指针就能够让其指向同一份空间,第一个对象开辟空间,而拷贝,赋值则将该空间的指针进行拷贝,并将该指针指向的计数++或者–即可

namespace tzc
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr=nullptr)
			:_ptr(ptr)
			,_count(new int(1))_mutex(new mutex)
		{
			cout << "构造函数获取资源" << endl;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_count(sp._count)
			,_mutex(sp._mutex)
		{
			AddRef();
			
		}
		 
		shared_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			//管理的是同一份资源不处理
			if (_ptr !=sp._ptr )
			{
				ReleaseRef();
				 
				_count = sp._count;
				_ptr = sp._ptr;
				_mutex = sp._mutex;
				AddRef();
			}
			return *this;
		}
		~shared_ptr()
		{
			ReleaseRef();
		}
	public:
		T* _ptr;
		int* _count;
		mutex* _mutex;//保证线程安全
	private:
		void AddRef()
		{
			_mutex.lock();
			++(* _count);
			_mutex.unlock();
		}
		void ReleaseRef()
		{
			_mutex.lock();
			int flag = 0;
			if (--(*_count) == 0)
			{
				if(_ptr)
				{
				cout << "delete" << _ptr << endl;
				delete _ptr;
				}
				delete _count;
				flag = 1;
			}
			_mutex.unlock();
			if (flag == 1)
			{
				delete _mutex;
			}
		}
	};
};
int main()
{
	tzc::shared_ptr<string> sp1(new string("hello world"));//将资源托管给sp
	tzc::shared_ptr<string> sp2(sp1);
	cout << *(sp1._count) << endl;
	return 0;
}

C++智能指针_第3张图片

循环引用

在如下场景:如果我们用shared_ptr来管理,就会造成循环引用

struct ListNode
{
	tzc::shared_ptr<ListNode> _next;
	tzc::shared_ptr<ListNode> _prev;

	int _val;

	~ListNode()
	{
		cout << "调用~ListNode()" << endl;
	}
};


int main()
{
	tzc::shared_ptr<ListNode> node1(new ListNode);
	tzc::shared_ptr<ListNode> node2(new ListNode);
	// 循环引用
	node1->_next = node2;
	node2->_prev = node1;
	// ...
	//delete node1;
	//delete node2;
	return 0;
}

C++智能指针_第4张图片

C++智能指针_第5张图片
由于_count一直为1,智能指针也不会delete释放掉管理的资源,会导致没有调用ListNode的析构函数,造成内存泄漏问题

那如何解决循环引用问题呢——weak_ptr

④weak_ptr

weak_ptr有点像shared_ptr,只是weak_ptr不会增加引用计数

	template<class T>
	class weak_ptr//不参与资源的管理 不delete shared_ptrdelete
	{
	public:
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{
			cout << "构造函数获取资源" << endl;
		}
		weak_ptr()
			:_ptr(nullptr)
		{
			cout << "默认构造函数获取资源" << endl;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		 

		weak_ptr<T>& operator=(shared_ptr<T>& sp)
		{
			//管理的是同一份资源不处理
			_ptr = sp.get();
			return *this;
		}
	
		T* get()const
		{
			return _ptr;
		}
		int use_count()
		{
			return *_count;
		}
	private: 
		T* _ptr;
		int* _count;
		mutex* _mutex;
		//保证线程安全
	private:
		
	};
};
struct ListNode
{
	tzc::weak_ptr<ListNode> _next;
	tzc::weak_ptr<ListNode> _prev;

	int _val;

	~ListNode()
	{
		cout << "调用~ListNode()" << endl;
	}
};


int main()
{
	tzc::shared_ptr<ListNode> node1(new ListNode);
	tzc::shared_ptr<ListNode> node2(new ListNode);
	// 循环引用
	node1->_next = node2;
	node2->_prev = node1;
	// ... 
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

C++智能指针_第6张图片
weak_ptr不会增加shared_ptr管理资源的引用计数,也不会参与资源的管理(shared_ptr管理),但可以像指针一样去使用

在这种shared_ptr搭配weak_ptr使用下,出作用域局部变量shared_ptr析构计数由一置零,会调用delete ptr,释放资源,实现类释放的同时,其成员变量也一并释放

定制删除器

由于智能指针析构用的是delete ptr,所以有些类型无法支持,例如开辟的数组对象,打开文件等等,此时需要传入可调用对象来定制删除

template<class T>
struct DelArr
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
	}
};
// 定制删除器 -- 删除器控制释放资源的方式
int main()
{
	std::shared_ptr<ListNode> spArr(new ListNode[10], DelArr<ListNode>());
	std::shared_ptr<FILE> spfl(fopen("test.txt", "w"), [](FILE* ptr){
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	});
	return 0;
}

C++智能指针_第7张图片

你可能感兴趣的:(C++,c++,开发语言,visualstudio)