智能指针
- 🏞️1. 为什么引入智能指针?
 - 🌁2. 智能指针的使用及原理
 - 📖2.1 RAII思想
 - 📖2.2 智能指针的原理
 
- 🌠3. 常见智能指针
 - 📖3.1 auto_ptr
 - 📖3.2 unique_ptr
 - 📖3.3 shared_ptr
 - 📖3.4 shared_ptr的循环引用问题
 - 📖3.5 weak_ptr
 
- 🌌4. 定制删除器
 - ⛺5. C++11和boost智能指针的关系
 
🏞️1. 为什么引入智能指针?
我们来看这样一段代码:
#include <iostream>
using namespace std;
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
	{
		throw "除0错误";
	}
	return a / b;
}
void func()
{
	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;
}
 
在这段代码中,如果在div函数中发生了除0错误,我们在main函数捕获异常,那么最终异常抛出后会跳转到main函数中的catch处,对于p1和p2申请的资源就没有得到释放,就造成了内存泄露问题.
关于内存泄露介绍,在另一篇文章中有详细的介绍:
🌁2. 智能指针的使用及原理
📖2.1 RAII思想
RAII是一种利用对象生命周期来控制程序资源(例如内存、文件句柄、网络连接、互斥量等)的简单技术.
获取到资源以后去初始化一个对象,将资源交给对象管理:资源获取即初始化
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源. 借此,我们实际是把管理一份资源的责任托管给了一个对象,这种做法有两大好处:
- 不需要显式的释放资源
 - 采用这种方式,对象所需的资源在其生命周期内始终有效
 
//实现一个最简易的智能指针
template<class T>
class smart_ptr
{
public:
	smart_ptr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	
    //对象析构时自动释放所管理的资源
	~smart_ptr()
	{
		cout << "delete " << _ptr << endl;
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}
private:
	T* _ptr;
};
 
此时,我们使用智能指针来代替裸指针,并让它发生除0错误:
#include <iostream>
#include "smart_ptr.h"
using namespace std;
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
	{
		throw invalid_argument("除0错误");
	}
	return a / b;
}
void func()
{
	smart_ptr<int> p1(new int);
	smart_ptr<int> p2(new int);
	cout << div() << endl;
}
int main()
{
	try
	{
		func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}
 

可以看到,刚才由于抛异常未能释放的资源现在可以正常释放.
📖2.2 智能指针的原理
我们所写的这个简单的智能指针smart_ptr还不能称其为智能指针,因为它还不具有指针的行为,指针可以解引用,可以通过->去访问所指向空间内的内容,所以为了让它向指针一样,我们还需重载*,->运算符.
template<class T>
class smart_ptr
{
public:
	smart_ptr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~smart_ptr()
	{
		cout << "delete " << _ptr << endl;
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}
	T& operator*() const
	{
		return *_ptr;
	}
	T* get() const
	{
		return _ptr;
	}
	T* operator->() const  //T*  = const T* _ptr
	{
		return _ptr;
	}
private:
	T* _ptr;
};
 
但是这样的智能指针是有问题的,试一下它的拷贝?

那么,怎么去解决这个问题呢?
所以,接下来,我们来介绍几种C++标准库里的智能指针,来探究如何解决此问题
🌠3. 常见智能指针
📖3.1 auto_ptr
C++98版本的库中就提供了auto_ptr智能指针,它解决了拷贝赋值的问题,但也还有一些不足
auto_ptr实现原理:管理权转移的思想,下面将简化的模拟实现auto_ptr,主要体现它的思想:
namespace myPtr
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete " << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}
		auto_ptr(auto_ptr<T>& sp)
			: _ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)
			{
				//释放当前对象管理的资源
				if (_ptr)
					delete _ptr;
				//管理权转移
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
            
            return *this;
		}
		T& operator*() const
		{
			return *_ptr;
		}
		T* operator->() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}
 
从它的拷贝构造和赋值重载可以看出,它以资源管理权转移的方式解决拷贝的问题.
但是,这样也带来另一个问题:
int main()
{
	myPtr::auto_ptr<int> p1(new int);
	myPtr::auto_ptr<int> p2 = p1;
	//*p1 = 10;  p1已经没有对资源的管理权,不能再使用!
	return 0;
}
 
由于p1的资源管理权已经转移给了p2,那它自己就失去对资源的管理及使用权,造成p1悬空.
📖3.2 unique_ptr
unique_ptr是C++11才开始提供的一种智能指针,它相比于auto_ptr更靠谱.
unique的实现原理:简单粗暴的禁止拷贝
我们依然是模拟实现一个unique_ptr来理解它的原理:
template<class T>
class unique_ptr
{
    public:
    unique_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        {}
    ~unique_ptr()
    {
        if (_ptr)
        {
            cout << "delete " << _ptr << endl;
            delete _ptr;
            _ptr = nullptr;
        }
    }
    unique_ptr(const unique_ptr<T>&) = delete;
    unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
    T& operator*() const
    {
        return *_ptr;
    }
    T* operator->() const
    {
        return _ptr;
    }
    
    private:
    T* _ptr;
};
 
unique_ptr采用一种简单粗暴的方式来解决拷贝的问题:直接删除拷贝构造函数和赋值重载函数,禁止拷贝
📖3.3 shared_ptr
shared_ptr也是C++11开始提供的,它能够解决智能指针拷贝的问题,并且它不像unique_ptr那样直接禁止拷贝,它是支持拷贝的.
shared_ptr的原理:通过引用计数的方式来实现多个shared_ptr对象之间共享资源
- 在
shared_ptr内部,给它所管理的资源维护了一份引用计数,用来记录该资源被几个对象共同管理(共享) - 在对象被销毁时(调用析构函数),就说明自己不使用该资源了,对用的引用计数减1
 - 如果引用计数减到0,就说明当前自己已经是最后一个使用该资源的对象,所以此时必须释放该资源
 - 如果不是0.那就说明还有其他对象管理这份资源,此时只需将引用计数减减即可,不需要释放资源
 
template<class T>
class shared_ptr
{
    public:
    shared_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        {
            //将引用计数初始化为1
        }
    
    ~shared_ptr()
    {
        if (_ptr)
        {
            cout << "delete " << _ptr << endl;
            delete _ptr;
            _ptr = nullptr;
        }
    }
    T& operator*() const
    {
        return *_ptr;
    }
    T* operator->() const
    {
        return _ptr;
    }
    private:
    T* _ptr;
    //维护一个引用计数
};
 
那么,这个引用计数,我们应该怎样去维护呢?
-  
使用普通变量
int _pCount显然不行,由于私有成员变量是两个对象独有的,假如我们有两个智能指针
p1和p2,对p1的引用计数的–不会影响p2 -  
定义一个静态成员变量
static int _pCount看似,好像可以,但是试一下如下场景:
template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) : _ptr(ptr) { //在构造函数中将引用计数初始化为1 _pCount = 1; } ~shared_ptr() { if (--_pCount == 0 && _ptr) { cout << "delete " << _ptr << endl; delete _ptr; _ptr = nullptr; } } shared_ptr(const shared_ptr<T>& sp) { _ptr = sp._ptr; ++_pCount; } T& operator*() const { return *_ptr; } T* operator->() const { return _ptr; } private: T* _ptr; //维护一个引用计数 static int _pCount; }; template<class T> int shared_ptr<T>::_pCount = 0;int main() { //_pCount = 1 myPtr::shared_ptr<int> p1(new int); //_pCount = 2; myPtr::shared_ptr<int> p2(p1); //这一步又重新将_pCount置为1,导致最终只释放的一次资源 myPtr::shared_ptr<int> p3(new int); return 0; } -  
定义一个指针成员变量
int* _pCounttemplate<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) : _ptr(ptr) , _pCount(new int(1)) {} void Release() { if (--(*_pCount) == 0 && _ptr) { cout << "delete " << _ptr << endl; delete _ptr; _ptr = nullptr; delete _pCount; _pCount = nullptr; } } ~shared_ptr() { Release(); } shared_ptr(const shared_ptr<T>& sp) { _ptr = sp._ptr; _pCount = sp._pCount; ++(*_pCount); } shared_ptr<T>& operator=(const shared_ptr<T>& sp) { //防止自赋值 if (_ptr != sp._ptr) { Release(); _ptr = sp._ptr; _pCount = sp._pCount; ++(*_pCount); } return *this; } T& operator*() const { return *_ptr; } T* operator->() const { return _ptr; } private: T* _ptr; //维护一个引用计数 int* _pCount; };这就是我们最终模拟实现出的
shared_ptr. 
📖3.4 shared_ptr的循环引用问题
shared_ptr的循环引用问题:
struct ListNode
{
	ListNode(const int& val = int())
		: _next(nullptr)
		, _prev(nullptr)
		, _val(val)
	{}
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
	myPtr::shared_ptr<ListNode> _next;
	myPtr::shared_ptr<ListNode> _prev;
	int _val;
};
int main()
{
	myPtr::shared_ptr<ListNode> p1(new ListNode(1));
	myPtr::shared_ptr<ListNode> p2(new ListNode(2));
	p1->_next = p2;
	p2->_prev = p1;
	return 0;
}
 
在上面的代码中,我们应该是有两份ListNode节点需要释放,运行程序:

没有任何节点被释放.

这便是shared_ptr的循环引用问题.
解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以
📖3.5 weak_ptr
weak_ptr原理:
node1->_next = node2;
node2->_prev = node1;
//weak_ptr的_next和_prev不会增加node1和node2的引用计数
 
weak_ptr模拟实现
template<class T>
class weak_ptr
{
    public:
    weak_ptr()
    {}
    weak_ptr(const shared_ptr<T>& sp)
    {
        _ptr = sp._ptr;
    }
    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;
    }
    private:
    T* _ptr;
};
 
🌌4. 定制删除器
在我们写的智能指针的析构函数中,统一都使用delete来释放资源,但是,如果资源不是用new申请出来的呢?比如:new T[],malloc,所以我们就需要定制删除器来规范释放资源的方式.
我们使用shared_ptr来做演示:
template<class T>
struct default_delete
{
    void operator()(T* ptr)
    {
        delete ptr;
    }
};
template<class T, class Del = default_delete<T>>
class shared_ptr
{
    public:
    shared_ptr(T* ptr = nullptr)
        : _ptr(ptr)
            , _pCount(new int(1))
        {}
    void Release()
    {
        if (--(*_pCount) == 0 && _ptr)
        {
            cout << "delete " << _ptr << endl;
            Del del;
            del(_ptr);
            _ptr = nullptr;
            delete _pCount;
            _pCount = nullptr;
        }
    }
    ~shared_ptr()
    {
        Release();
    }
    shared_ptr(const shared_ptr<T>& sp)
    {
        _ptr = sp._ptr;
        _pCount = sp._pCount;
        ++(*_pCount);
    }
    shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
        //防止自赋值
        if (_ptr != sp._ptr)
        {
            Release();
            _ptr = sp._ptr;
            _pCount = sp._pCount;
            ++(*_pCount);
        }
        return *this;
    }
    T& operator*() const
    {
        return *_ptr;
    }
    T* operator->() const
    {
        return _ptr;
    }
    T* get() const
    {
        return _ptr;
    }
    private:
    T* _ptr;
    //维护一个引用计数
    int* _pCount;
};
 
//定制new T[]类型的删除器
template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};
int main()
{
	myPtr::shared_ptr<int, DeleteArray<int>> p(new int[10]);
	return 0;
}
 
⛺5. C++11和boost智能指针的关系
- C++98中产生了第一个智能指针
auto_ptr - C++ boost库给出了更实用的scoped_ptr和shared_ptr以及weak_ptr
 - C++ TR1,引入了shared_ptr等,不过需要注意的是TR1并不是标准版
 - C++ 11,引入了unique_ptr和shared_ptr以及weak_ptr,需要注意的是unique_ptr对应的boost的scoped_ptr,并且这些智能指针的实现原理是参考了boost库中的.
 


















