C++:智能指针

目录

一.智能指针的使用及原理

1.RAII

智能指针示例:bit::SmartPtr sp1(new int);

2.auto_ptr——C++探索路上的失败品

3.unique_ptr

unique_ptr总实现

4.shared_ptr

二.shared_ptr的致命缺点-循环引用

1.循环引用

2.weak_ptr

weak_ptr实现代码

三.定制删除器

四.C++11和boost中智能指针的关系


一.智能指针的使用及原理

1.RAII

RAII :是智能指针实现的指导思想。RAII≠智能指针
解释:获取到资源以后去初始化一个对象,将资源交给对象管理
 
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内
存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效

智能指针示例:bit::SmartPtr<int> sp1(new int);

解释:无论sp1,sp2,sp3,sp4这四个哪里的new出异常,都没问题,比如sp3的new抛异常了,sp1和sp2照样可以成功释放;如果sp1的new抛异常了,则直接跳到main的catch中结束;如果函数div抛异常了,sp1,sp2,sp3,sp4照样可以成功释放;

class SmartPtr
	{
	public:
		// RAII思想
		SmartPtr(T* ptr)
			:_ptr(ptr)
		{}

		~SmartPtr()
		{
			cout <<"delete"<<_ptr << endl;
			//delete[] _ptr;
			delete _ptr;
			_ptr = nullptr;
		}

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

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

		T* Get()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

double div()
{
	double a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	bit::SmartPtr<int> sp1(new int);
	bit::SmartPtr<int> sp2(new int);
	bit::SmartPtr<int> sp3(new int);
	bit::SmartPtr<int> sp4(new int);
	bit::SmartPtr<pair<string, int>> sp5(new pair<string, int>("sort", 1));

	*sp1 = 0;
	sp5->second++;

	cout << div() << endl;
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
		// ...
	}

	return 0;
}

2.auto_ptr——C++探索路上的失败品

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。 auto_ptr的实现原理:管理权转移的思想。

sp2=sp1进行拷贝构造,使sp2指向sp1的资源,随后直接把sp1置空了,由sp2接管了sp1的资源,导致sp1无法进行访问,即:管理权转移,被拷贝的对象悬空。很多公司明确要求,不能使用auto_ ptr

template<class T>
	class auto_ptr
	{
	public:
		// RAII思想
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

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

		// sp2(sp1)
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		}

		// ...

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

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

		T* get()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};


int main()
{
	bit::auto_ptr<int> sp1(new int);
	bit::auto_ptr<int> sp2 = sp1;

	// sp1悬空
	// *sp1 = 10;
	*sp2 = 20;

	return 0;
}

3.unique_ptr

 unique_ptr的实现原理:因为auto_ptr拷贝有风险,所以unique_ptr直接没风险了:简单粗暴的防止拷贝/不让拷贝,拷贝编译就报错。

 unique_ptr仅仅只是把拷贝构造和拷贝赋值进行删除:=delete 作用是删除该函数,使该函数不可调用

 以前C++98的方式:已放弃的方式
	//private:
	//	// sp2(sp1)
	//	//
	//	// 1、只声明,不实现
	//	// 2、声明成私有
	//	unique_ptr(const unique_ptr<T>& sp);

 C++11的方式:
		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

unique_ptr总实现

template<class T>
	class unique_ptr
	{
	public:
		// RAII思想
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

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

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

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

		T* get()
		{
			return _ptr;
		}


	//private:
	//	// sp2(sp1)
	//	// C++98
	//	// 1、只声明,不实现
	//	// 2、声明成私有
	//	unique_ptr(const unique_ptr<T>& sp);

		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	private:
		T* _ptr;
	};
int main()
{
	bit::unique_ptr<int> up1(new int);
	//bit::unique_ptr<int> up2(up1);
	bit::unique_ptr<int> up2(new int);

	//up1 = up2;

	return 0;
}

4.shared_ptr

shared_ptr:核心原理就是引用计数,记录几个对象管理这块资源,析构的时候一一计数,最后一个析构的对象释放资源

为了使每个资源都有一个计数,在构造函数中定义一个_pCount(new int(1)) ,拷贝构造时就 (*_pCount)++。如果*_pCount不为0,说明有多个对象指向这块资源,只需      --(*_pCount)即可;析构时只让最后一个指向资源的对象释放,即:*_pCount=0时进行释放,这样防止了多次析构;

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。例如:
比特老师晚上在下班之前都会通知,让最后走的学生记得把门锁下。
1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减
一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对
象就成野指针了。
	template<class T>
	class shared_ptr
	{
	public:
		void Release()
		{
			if (--(*_pCount) == 0 && _ptr)
			{
				cout << "delete" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;

				delete _pCount;
				_pCount = nullptr;
			}
		}

		// RAII思想
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pCount(new int(1))
		{}

		~shared_ptr()
		{
			Release();
		}

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

		

		// sp1 = sp3
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
				Release();

				_ptr = sp._ptr;
				_pCount = sp._pCount;
				++(*_pCount);
			}

			return *this;
		}


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

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

		T* get()
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* _pCount;
	};
int main()
{
	bit::shared_ptr<int> sp1(new int);
	bit::shared_ptr<int> sp2(sp1);

	bit::shared_ptr<int> sp3(new int);

	sp1 = sp1;
	sp1 = sp2;

	sp1 = sp3;
	sp2 = sp3;

	return 0;
}

二.shared_ptr的致命缺点-循环引用

1.循环引用

我们让p1和p2这两个节点互相指向:p1的_next指向p2,p2的_prev指向p1

这样会出现一些问题:因为原本是p1管理第一个节点,p2管理第二个节点。互相指向后,p1和p2的_prev共同管理第一个节点;互相指向后,p2和p1的_next共同管理第二个节点;这样导致这两个节点的管理计数都是2。出main函数时,p1对象在后面,则先析构,因为管理计数是2,所以计数减1,p2对象析构时,管理计数也是2,所以也是计数减1。

①p2受最后一个指向资源的对象释放,这个对象就是_next,所以p2如果想释放,就要依赖p1的_next析构,但是_next是p1对象的成员变量,_next想析构需要p1对象这个节点释放;

②p1如果想释放,就要依赖p2的_prev析构,但是_prev是p2对象的成员变量,_prev想析构需要p2这个对象释放;但是p2又需要p1的_next析构……又回到①号问题了。这就叫做循环引用

板书介绍(跟上面一样,可以不看):

_prev 管着左边的节点
_next 管着右边的节点
他们分别是两个节点自定义成员
节点析构释放,_prev/_next这些里面的成员才析构释放

左边节点释放, next析构
_prev析构, 左边节点释放
右边节点释放,prev析构
右边节点释放,依赖_next

为了解决循环引用,需要用到weak_ptr:

2.weak_ptr

辅助shared_ptr解决循环引用。

使对象只指向资源,不管理资源(就是不需要释放资源)

作用:std: :weak_ptr<ListNode> _next;  p1->_next=:p2;  可以使_next不参与管理,即:可以访问,但是资源的释放还是p2释放,_next不参与释放资源,_next指向p2管理的资源时并不会增加计数。

(use_count()用于查看shared_ptr的计数个数)

weak_ptr实现代码

——————————————————————————————————————.h文件
	// 不参与指向资源的释放管理
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

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

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp.get())
			{
				_ptr = sp.get();
			}

			return *this;
		}

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

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

	public:
		T* _ptr;
	};

——————————————————————————————————————test.cpp

struct ListNode
{
	/*ListNode* _next = nullptr;
	ListNode* _prev = nullptr;*/

	//bit::shared_ptr<ListNode> _next = nullptr;
	//bit::shared_ptr<ListNode> _prev = nullptr;

	bit::weak_ptr<ListNode> _next;
	bit::weak_ptr<ListNode> _prev;

	int _val = 0;

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

int main()
{
	// 循环引用
	bit::shared_ptr<ListNode> p1(new ListNode);
	bit::shared_ptr<ListNode> p2(new ListNode);

	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;

	p1->_next = p2;
	p2->_prev = p1;

	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;

	return 0;
}

三.定制删除器

unique_ptr 第二个参数是删除器,由于缺省参数默认是 default_delete<T> ,default_delete<T>中就是delete,但是如果你需要析构[]用delete会报错,malloc出来的也不能用delete,等等,所以我们要定制删除器:bit::unique_ptr<Date, DeleteArray<Date>> up2(new Date[10]); 

DeleteArray<Date> 写一个DeleteArray 的类并重载operator(),operator()内部自己定制释放方式,删除器参数传仿函数类型,就叫定制删除器

cpp文件

class Date
{
public:
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

// unique_ptr/shared_ptr  默认释放资源用的delete
// 如何匹配申请方式去对应释放呢?

template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		cout <<"delete[]"<<ptr << endl;

		delete[] ptr;
	}
};

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		cout << "free" << ptr << endl;

		free(ptr);
	}
};

struct Fclose
{
	void operator()(FILE* ptr)
	{
		cout << "fclose" << ptr << endl;

		fclose(ptr);
	}
};

// 定制删除器
int main()
{
	// 传类型  21:01继续
	bit::unique_ptr<Date> up1(new Date);
	bit::unique_ptr<Date, DeleteArray<Date>> up2(new Date[10]);
	bit::unique_ptr<Date, Free<Date>> up3((Date*)malloc(sizeof(Date)* 10));
	bit::unique_ptr<FILE, Fclose> up4((FILE*)fopen("Test.cpp", "r"));

	// 传对象
	std::shared_ptr<Date> sp1(new Date[10], DeleteArray<Date>());
	std::shared_ptr<Date> sp2(new Date[10], [](Date* ptr){delete[] ptr; });

	return 0;
}

 std::shared_ptr<Date> sp1(new Date[10], DeleteArray<Date>()); 可以直接构造传匿名对象

.h文件

template<class T>
	struct default_delete
	{
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};

template<class T, class D = default_delete<T>>
	class unique_ptr
	{
	public:
		// RAII思想
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				//cout << "delete" << _ptr << endl;
				//delete _ptr;
				D del;        //定制删除器
				del(_ptr);

				_ptr = nullptr;
			}
		}

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

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

		T* get()
		{
			return _ptr;
		}


	//private:
	//	// sp2(sp1)
	//	// C++98
	//	// 1、只声明,不实现
	//	// 2、声明成私有
	//	unique_ptr(const unique_ptr<T>& sp);

		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	private:
		T* _ptr;
	};

四.C++11和boost中智能指针的关系

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

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值