论智能指针的发展史

RAII:

1.RAII的由来:

     我们写的有关资源管理的代码如果出现bug,会导致下面的问题:

●malloc出来的空间,没有进行释放,存在内存泄漏的问题。

异常安全问题。如果在malloc和free之间如果存在抛异常,那么还是有内存泄漏。这种问题就叫异常安全。

      也许会有人说,那我写代码的时候小心点就行了,但是天算不如人算,当工程量浩大的时候总避免不了这样那样的问题,因此就出现了一种叫做RAII的技术。

2.什么是RAII?

RAII是一种利用对象生命周期来控制程序资源(如内存,互斥量)的技术。 

RAII思想:1>用一个类封装资源的分配和释放,把管理一份资源的责任托管给了一个对象。 

                  2>在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效。

                  3>最后在对象析构的时候释放资源。

template<class T>
class RAII
{
public:
	RAII(T* ptr=nullptr)
		:_ptr(ptr)
	{}
	~RAII()
	{
		if (_ptr)
			delete_ptr;
	}
private:
	T* _ptr;
};

int main()
{
	try
	{
		int* p = (int*)malloc(sizeof(int));
		RAII<int> sp(p);

		//.....sp处理事务
		//........
	}
	catch(const exception& e)
	{
		cout << e.what() << endl;
	}
	system("pause");
	return 0;
}

好处:

不需要显式地释放资源。即使我们忘记释放malloc出来的空间或者由于抛异常而导致空间没有释放,RAII对象都会帮你的,对象出了作用域自动调用析构函数,资源被释放。
●采用这种方式,对象所需的资源在其生命期内始终保持有效。

智能指针:

一、智能指针的特点

具有RAII特性。
具有像指针一样的行为,重载operator*和opertaor->。

      使用智能指针,可以实现自动的内存管理,不需要担心忘记delete造成的内存泄漏。

二、智能指针分类

     auto_ptr、unique_ptr、shared_ptr、weak_ptr

三、各种智能指针的原理及实现

1.auto_ptr:

1>实现原理:

       管理权转移的思想,当把我的资源交给你后,我就置空,也就是说任何时候只能有一个人管理这块资源。这样其实是对的,如果我不置空,那么我们两个同时管理这块资源,那析构的时候资源释放两次岂不是有问题。

2>使用:

struct Date
{
	int _year;
	int _month;
	int _day;
};
void test()
{
	auto_ptr<Date> s1(new Date);
	s1->_year = 1998;
	s1->_month = 6;
	s1->_day = 30;
	auto_ptr<Date> s2(s1);//导致s1悬空
	s2->_year = 2019;
	s2->_month = 2;
	s2->_day = 17;

	//s1->_day = 1998;程序崩溃
}

3>缺点:

    auto_ptr导致原对象悬空,这正是它的缺点,s1的资源已经被释放,但是很容易让人误用s1的资源,就会造成程序崩溃。

4>模拟实现:

template<class T>
class Auto_Ptr
{
public:
	Auto_Ptr(T* ptr)
		:_ptr(ptr)
	{}
	
	Auto_Ptr(Auto_Ptr<T>& copy)
		:_ptr(copy._ptr)
	{
		copy._ptr = nullptr;
	}

	Auto_Ptr<T>& operator=(Auto_Ptr<T>& copy)
	{
		if (this != copy)
		{
			//释放当前对象资源
			delete _ptr;

			//转移资源
			_ptr = copy._ptr;
			copy->_ptr = nullptr;
		}

		return *this;
	}

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

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

	~Auto_Ptr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};

2.unique_ptr

1>原理:简单粗暴,不让拷贝和赋值。既然auto_ptr拷贝对象会导致悬空问题,那我就不让你拷贝,这样是不是安全多了。

2>使用:

void test()
{
	Unique_Ptr<Date> s1(new Date);
	s1->_year = 1998;
	s1->_month = 6;
	s1->_day = 30;
	//unique_Ptr<Date> s2(s1);编译不通过
}

3>实现:

template<class T>
class Unique_Ptr
{
public:
	Unique_Ptr(T* ptr)
		:_ptr(ptr)
	{}

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

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

	~Unique_Ptr()
	{
		if (_ptr)
			delete _ptr;
	}
	
private:
	//C++98防拷贝
	/*Unique_Ptr(const Unique_Ptr<T>& );
	Unique_Ptr<T>& operator=(const Unique_Ptr<T>&);*/

	//C++11
	Unique_Ptr(const Unique_Ptr<T>&) = delete;
	Unique_Ptr<T>& operator=(const Unique_Ptr<T>&) = delete;

	T* _ptr;
};

3.shared_ptr:

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

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

2>使用:

void test()
{
	//引用计数
	shared_ptr<Date> s1(new Date);
	s1->_year = 2019;
	s1->_month = 2;
	s1->_day = 18;

	shared_ptr<Date> s2(s1);
	cout << "s1_use" << s1.use_count() << endl;//2
	shared_ptr<Date> s3(s1);

	cout << "s1_use" << s1.use_count() << endl;//3
	cout << "s3_use" << s2.use_count() << endl;//3
	cout << "s3_use" << s3.use_count() << endl;//3
}

3.模拟实现:

template<class T>
class Shared_Ptr
{
public:
	Shared_Ptr(T* ptr = nullptr)
		:_ptr(ptr)
		,_count(new int(1))
		, _mutex(new mutex)
	{
		if (_ptr == nullptr)
			*_count=0;
	}

	Shared_Ptr(const Shared_Ptr<T>& copy)
		:_ptr(copy._ptr)
		,_count(copy._count)
		,_mutex(copy._mutex)
	{
		//ptr不为空增加引用计数
		if (_ptr)
			AddCount();
	}

	Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp)
	{
		if (this != &sp)
		{
			//释放当前对象资源
			Release();
			//赋值
			_ptr = sp._ptr;
			_count = sp._count;
			_mutex = sp._mutex;
		
			if (_ptr)
				AddCount();
		}
		return *this;

	}

	~Shared_Ptr()
	{
		Release();
	}

	int AddCount()
	{
    //原子操作
		_mutex->lock();
		(*_count)++;
		_mutex->unlock();

		return *_count;
	}

	int SubCount()
	{
		_mutex->lock();
		(*_count)--;
		_mutex->unlock();

		return *_count;
	}

	T& UseCount()
	{
		return *_count;
	}

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

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

private:
	void Release()
	{
		if (_ptr == nullptr)
			delete _mutex;
        
        //引用即使减到零则释放资源
		if (_ptr&& SubCount() == 0)
		{
			delete _ptr;
			delete _count;
			delete _mutex;
		}
	}
private:
	T* _ptr;
	int* _count;//引用计数
	mutex* _mutex;//互斥锁
};

这种方法可以很好的实现拷贝构造,也不会造成前几种指针的问题。堪称完美,但是在某些方面依然有缺陷。比如:

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

	~ListNode()
	{
		cout << "~ListNode" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> s1(new ListNode);
	shared_ptr<ListNode> s2(new ListNode);
	cout << "s1 " << s1.use_count() << endl;
	cout << "s2 " << s2.use_count() << endl;//1

	s1->_next = s2;
	s2->_prev = s1;
	cout << "s1 " << s1.use_count() << endl;
	cout << "s2 " << s2.use_count() << endl;//2
	//出了main后 s1s2析构,但是s1->next和s2->prev还存在,这样谁都不会释放
}

上面的问题就叫做循环引用,也就是s1 s2绑在了一起,都等着对方释放,但是他们两个谁也不释放。

4.weak_ptr:专门为了收拾shared_ptr造成的循环引用问题。

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

2>使用:上面的问题使用weak_ptr就没有任何问题了。

//weak_ptr解决
struct ListNode
{
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;

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

void test()
{
	shared_ptr<ListNode> s1(new ListNode);
	shared_ptr<ListNode> s2(new ListNode);
	s1->_next = s2;
	s2->_prev = s1;
	cout << "s1 " << s1.use_count() << endl;//1
	cout << "s2 " << s2.use_count() << endl;//1
}

3>实现:

template<class T>
class WeakPtr
{
public:
    WeakPtr(SharedPtr<T>& sp)//匿名对象
        :_ptr(sp._ptr)//将sharedptr的对象给weakptr(弱指针)进行初始化,weakptr指向sharedptr的对象
    {}

    WeakPtr()
        :_ptr(NULL)
    {}

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

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

总结:每种智能指针都有自己的特点,但使用哪一种还是要具体情况具体分析。

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值