c++修炼之路之智能指针

目录

一:智能指针的必要性 

二:C++11和boost中智能指针的关系

三:智能指针的使用及原理

1.RAII

2.智能指针的原理

3.底层实现 

四:内存泄露 

接下来的日子会顺顺利利,万事胜意,生活明朗-----------林辞忧 

一:智能指针的必要性 

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

这里对于p1这里new抛异常时,会在main函数中捕获到异常进行处理,Func函数后面的代码不再执行,这里还不会出现啥问题的,如果是p2这里new抛异常时,也会在main函数中捕获到异常进行处理,此时后面的代码不再执行,造成p1没有释放,即内存泄露,对于div调用时抛异常,p1和p2都没有释放也造成内存泄露。因此对于抛异常时,对于动态申请的数据是很难做到全部释放,不会内存泄露的,此时就可以使用智能指针来解决上述问题

二: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中的实现的。

三:智能指针的使用及原理

1.RAII

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

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效

2.智能指针的原理

1.SmartPtr指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用

template<class T>
class SmartPtr
{
public:
	SmartPtr(T*ptr=nullptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		if(_ptr)
		delete _ptr;
	}

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

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

struct Date
{
	int _year;
	int _month;
	int _day;

	/*Date(int year=1,int month=1,int day=1)
	{}*/
};
int main()
{
	SmartPtr<int> sp1(new int);
	*sp1 = 10;
	cout << *sp1 << endl;
	SmartPtr<Date> sparray(new Date);
	// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
	// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
	sparray->_year = 2018;
	sparray->_month = 1;
	sparray->_day = 1;
}

 2.使用auto_ptr来管理

这时在拷贝时就会发现程序崩溃的,这是因为拷贝时,管理权限转移,被拷贝悬空,ap1指针被置为空,此时在访问就会访问空指针的 

3.使用unique_ptr来管理

4.使用shared_ptr来管理

 5.当管理多个对象时

这时发现会使程序崩溃,因为底层默认为delete,这时不匹配就会出错,此时就可以使用定制删除器来解决问题

可以给个可调用对象,如lambda表达式,仿函数,函数指针

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

struct Fclose
{
	void operator()(FILE* file)
	{
		cout << "fclose:" << file << endl;
		fclose(file);
	}
};
int main()
{
	//定制删除器
	
	unique_ptr<Date,DeleteArray<Date>> up1(new Date[5]);
	unique_ptr<FILE,Fclose> up2(fopen("Text.cpp","r"));
	//提供一个特化的版本
	unique_ptr<Date[]> up3(new Date[3]);

	shared_ptr<Date[]> sp1(new Date[2]);
	shared_ptr<FILE>sp2(fopen("Text.cpp","r"),Fclose());
	shared_ptr<Date> sp3 = make_shared<Date>(2024,9,4);

	return 0;
}

3.底层实现 

1.auto_ptr关键函数

2.unique_ptr关键函数

3.shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减1
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对
象就成野指针了。
5. 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源 

#include <functional>
namespace mjw
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr=nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}

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

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

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

				_ptr = nullptr;
				_pcount = nullptr;
			}
		}

		//sp1=sp3
		shared_ptr& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)//防止自己给自己赋值
			{
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				(*_pcount)++;
			}

			return *this;
		}

		~shared_ptr()
		{
			release();
		}
		T* get()
		{
			return _ptr;
		}

		int use_count() const
		{
			return *_pcount;
		}

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

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

4.shared_ptr的循环引用

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

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

int main()
{
	// 循环引用 -- 内存泄露
	std::shared_ptr<ListNode> n1(new ListNode);
	std::shared_ptr<ListNode> n2(new ListNode);

	n1->_next = n2;
	n2->_prev = n1;

	return 0;
}

此时就需要使用weak_ptr来解决

// weak_ptr不支持管理资源,不支持RAII
//std::weak_ptr<ListNode> wp(new ListNode);

struct ListNode
{
	/*std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;*/

	//不增加引用计数
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;

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

node1->_next = node2和node2->_prev = node1时weak_ptr的_next和_prev不会增加node1和node2的引用计数。 

 5.weak_ptr的简单实现

template<class T>
class weak_ptr
{
public:
	weak_ptr()
	{}

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

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

		return *this;
	}
private:
	T* _ptr=nullptr;
};

四:内存泄露 

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。

内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄
漏检测工具

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值