C++中智能指针概念及模拟实现

1. 什么是智能指针?

智能指针是一种托管资源空间的技术,可以访问空间、模拟原生指针的行为,只是在原生指针生命周期结束时,释放资源。

这里涉及到另一个概念:RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的技术。即在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,在对象析构的时候释放资源,实际上就是把管理一份资源的责任托管给了一个对象,这样做带来了两个优点:①不需要显式的释放资源、②对象所需的资源在其生命周期内始终保持有效。

智能指针就是依靠 RAII 实现。

2. 提出智能指针的目的是什么?

如果申请了资源,未对资源进行释放,则就会造成内存泄漏;

在大型工程中,内存泄漏的危害是致命的,会导致系统的资源无法使用,该资源并不是在内存中消失了,而是失去了对该块内存的控制权。

另一种情况是,当出现异常安全的问题时,会导致内存泄漏,锁会导致死锁现象;

基于此,C++中提出了智能指针的概念,来避免因为内存泄漏而导致的系统性能下降的问题。

3. 智能指针的版本及简单模拟实现

3.1 auto_ptr

C++98中首次提出了auto_ptr,但是auto_ptr的设计中,存在着巨大的缺陷,导致现在使用的人很少;

其源码可在<memory>中查看:

template<class _Ty>
	class auto_ptr
	{	// wrap an object pointer to ensure destruction
public:
	typedef _Ty element_type;

	explicit auto_ptr(_Ty * _Ptr = nullptr) noexcept
		: _Myptr(_Ptr)
		{	// construct from object pointer
		}

	auto_ptr(auto_ptr& _Right) noexcept
		: _Myptr(_Right.release())
		{	// construct by assuming pointer from _Right auto_ptr
		}

	auto_ptr& operator=(auto_ptr& _Right) noexcept
		{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
		}
	void reset(_Ty * _Ptr = nullptr)
		{	// destroy designated object and store new pointer
		if (_Ptr != _Myptr)
			delete _Myptr;
		_Myptr = _Ptr;
		}
	_Ty * release() noexcept
		{	// return wrapped pointer and give up ownership
		_Ty * _Tmp = _Myptr;
		_Myptr = nullptr;
		return (_Tmp);
		}
	~auto_ptr() noexcept
		{	// destroy the object
		delete _Myptr;
		}
private:
	_Ty * _Myptr;	// the wrapped object pointer
	};

看源码中可以看出:auto_ptr的实现思想是管理权转移,当有对象拷贝或者赋值时,直接将该原对象置空,所拷贝的指向原空间。

这也是auto_ptr带来的缺点:会出现指针悬空的问题,若是不小心使用了该指针,程序就会报错。

3.2 boost库中智能指针

自C++98提出智能指针之后,该版本的指针带来了许多缺陷,不够完美,第三方库boost中,实现了自己的智能,并且有些指针后序被C++标准库所采用。

智能指针描述
scoped_ptr作用域指针,一个作用域指针占用一个动态分配的对象(不会发生管理权转移)
scoped_array作用域数组,析构函数使用 delete[] 操作符来释放所包含的对象。 该操作符只能用于数组对象,所以作用域数组必须通过动态分配的数组来初始化。
shared_ptr共享指针,该指针使用的非常多,并且已经被添加到了标准库中。shared_ptr 不一定要独占一个对象。 它可以和其他 shared_ptr 类型的智能指针共享所有权。 当引用对象的最后一个智能指针销毁后,对象才会被释放。
shared_array共享数组,共享数组的行为类似于共享指针。 不同在于共享数组在析构时,默认使用 delete[] 操作符来释放所含的对象。
weak_ptrweak_ptr 必须通过 shared_ptr 来初始化的。一旦初始化之后,它基本上只提供一个有用的方法: lock()。此方法返回的shared_ptr 与用来初始化弱指针的共享指针共享所有权。 当函数需要一个由共享指针所管理的对象,而这个对象的生存期又不依赖于这个函数时,就可以使用弱指针。 只要程序中还有一个共享指针掌管着这个对象,函数就可以使用该对象。 如果共享指针复位了,就算函数里能得到一个共享指针,对象也不存在了。
intrusive_ptr介入式指针,工作方式和共享指针完全一样,只不过需要自己计共享指针的数量
ptr_vector指针容器,专门用于动态分配的对象,它使用起来更容易也更高效

scoped_ptr部分源码:

template<class T> class scoped_ptr // noncopyable
{
private:

    T * px;

    scoped_ptr(scoped_ptr const &);
    scoped_ptr & operator=(scoped_ptr const &);

    typedef scoped_ptr<T> this_type;

    void operator==( scoped_ptr const& ) const;
    void operator!=( scoped_ptr const& ) const;

public:
    typedef T element_type;

    explicit scoped_ptr( T * p = 0 ) BOOST_SP_NOEXCEPT 
    : px( p )
    {}
    
     explicit scoped_ptr( std::auto_ptr<T> p ) BOOST_SP_NOEXCEPT 
    : px( p.release() )
    {}
    
    ~scoped_ptr() BOOST_SP_NOEXCEPT
    {
        boost::checked_delete( px );
    }    
} 

scoped_array部分源码:

template<class T> class scoped_array // noncopyable
{
private:

    T * px;

    scoped_array(scoped_array const &);
    scoped_array & operator=(scoped_array const &);

    typedef scoped_array<T> this_type;

    void operator==( scoped_array const& ) const;
    void operator!=( scoped_array const& ) const;

public:

    typedef T element_type;

    explicit scoped_array( T * p = 0 ) BOOST_SP_NOEXCEPT 
    : px( p )
    {}

    ~scoped_array() BOOST_SP_NOEXCEPT
    {
        boost::checked_array_delete( px );
    }
}

shared_ptr部分源码:

template<class T> class shared_ptr
{
private:
    element_type * px;                 // contained pointer
    boost::detail::shared_count pn;    // reference counter
public:
    typedef typename boost::detail::sp_element< T >::type element_type;

    BOOST_CONSTEXPR shared_ptr() BOOST_SP_NOEXCEPT 
    : px( 0 ), pn()
    {
    }
    BOOST_CONSTEXPR shared_ptr( boost::detail::sp_internal_constructor_tag, element_type * px_, boost::detail::shared_count && pn_ ) BOOST_SP_NOEXCEPT 
    : px( px_ ), pn( std::move( pn_ ) )
    {
    }
};

weak_ptr示例:

#include <windows.h> 
#include <boost/shared_ptr.hpp> 
#include <boost/weak_ptr.hpp> 
#include <iostream> 

DWORD WINAPI reset(LPVOID p) 
{ 
  boost::shared_ptr<int> *sh = static_cast<boost::shared_ptr<int>*>(p); 
  sh->reset(); 
  return 0; 
} 

DWORD WINAPI print(LPVOID p) 
{ 
  boost::weak_ptr<int> *w = static_cast<boost::weak_ptr<int>*>(p); 
  boost::shared_ptr<int> sh = w->lock(); 
  if (sh) 
    std::cout << *sh << std::endl; 
  return 0; 
} 

int main() 
{ 
  boost::shared_ptr<int> sh(new int(99)); 
  boost::weak_ptr<int> w(sh); 
  HANDLE threads[2]; 
  threads[0] = CreateThread(0, 0, reset, &sh, 0, 0); //Windows API 
  threads[1] = CreateThread(0, 0, print, &w, 0, 0); //Windows API 
  WaitForMultipleObjects(2, threads, TRUE, INFINITE); 
} 

第一个线程函数 reset() 的参数是一个共享指针的地址。 第二个线程函数 print() 的参数是一个弱指针的地址。 这个弱指针是之前通过共享指针初始化的。

一旦程序启动之后,reset() print() 就都开始执行了。 不过执行顺序是不确定的。 这就导致了一个潜在的问题:reset() 线程在销毁对象的时候print() 线程可能正在访问它。

通过调用弱指针的 lock() 函数可以解决这个问题:如果对象存在,那么 lock() 函数返回的共享指针指向这个合法的对象。否则,返回的共享指针被设置为0,这等价于标准的null指针。

弱指针本身对于对象的生存期没有任何影响。 lock() 返回一个共享指针,print() 函数就可以安全的访问对象了。 这就保证了——即使另一个线程要释放对象——由于我们有返回的共享指针,对象依然存在。

3.3 unique_ptr

C++标准库中参考boost库中scoped_ptr做的仿拷贝版智能指针;不允许拷贝和赋值

标准库中,将拷贝构造和赋值运算符重载声明为删除函数;

template<class _Ty, class _Dx>
class unique_ptr
{
public:
	unique_ptr(const unique_ptr&) = delete;
	unique_ptr& operator=(const unique_ptr&) = delete;
};

简单模拟实现:

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

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

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

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

	UniquePtr(const UniquePtr<T>&) = delete;//拷贝构造
	UniquePtr<T>& operator=(const UniquePtr<T>&) = delete;//赋值运算符重载
private:
	T* _ptr;
};

3.4 shared_ptr

共享指针,是通过引用计数的方式来实现多个共享指针对象之间共享资源。当引用对象的最后一个智能指针销毁后,对象才会被释放。

需要注意一点:引用计数的++, - -操作不是原子性的,容易引发线程安全问题,则在这里,进行了加锁操作;

模拟实现:

template<class T>
class SharedPtr
{
private:
	T* _ptr;//指针
	int* _referencecount;//引用计数
	mutex* _mutex;//互斥锁

public:
	SharedPtr()
		:_ptr(nullptr)
		, _referencecount(nullptr)
		, _mutex(nullptr)
	{}

	SharedPtr(T* ptr= nullptr)//构造函数
		:_ptr(ptr)
		, _referencecount(new int(1))
		,_mutex(new mutex)
	{}

	~SharedPtr()
	{
		Release();
	}

	SharedPtr(const SharedPtr<T>& sp)//拷贝构造
		:_ptr(sp._ptr)
		, _referencecount(sp._referencecount)
		, _mutex(sp._mutex)
	{
		Add_Ref_Count();//增加计数器数量
	}

	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			Release();//释放旧的共享资源,这步操作必须进行

			_ptr = sp._ptr;
			_referencecount = sp._referencecount;
			_mutex = sp._mutex;

			Add_Ref_Count();//增加计数器数量

		}
	}

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

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

	int UseCount()//当前引用计数共享数量
	{
		return *_referencecount;
	}

	T* Get()//获取当前共享指针
	{
		return _ptr;
	}
private:
	void Add_Ref_Count()// 增加计数器数量
	{
		_mutex->lock();
		++(*_referencecount);
		_mutex->unlock();
	}

	void Release()//释放资源
	{
		_mutex->lock();// 先加锁
		bool deletemutex = false;
		if (--(*_referencecount) == 0)//如果等于0,则表示可以释放资源了
		{
			delete _ptr;
			delete _referencecount;
			deletemutex = true;
		}
		_mutex->unlock();//解锁

		if (deletemutex)//释放互斥锁
			delete _mutex;
	}
};

共享指针经常使用到,但是也存在着另一个问题,即循环引用现象:

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

	~ListNode()
	{
		cout << "~Delete ListNode" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	node1->_prev = node2;
	node2->_next = node1;

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	return 0;
}

比如在这份代码中,就不会对对象进行析构;

在这里插入图片描述
这就是循环引用,此时的情况下,可使用weak_ptr,不会增加node1和node2中的引用计数,则资源就能得到合理释放。

struct ListNode
{
	int data;
	weak_ptr<ListNode> _prev;//弱指针,解决循环引用问题
	weak_ptr<ListNode> _next;

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

不是new出来的对象如何通过智能指针管理呢?

在shared_ptr中设计了一个删除器可以解决该问题。

该定制删除器传仿函数对象给智能指针,就可以用智能指针管理不是new出来的对象;

template<class T>
struct DeleteIntPointer//定制删除器
{
	void operator()(T* ptr)
	{
		if (ptr)
			delete ptr;
	}
};
int main()
{
	DeleteIntPointer<int> DIP;
	shared_ptr<int> sp((int*)malloc(20), DIP);//传入定制删除器对象,则可以删除不是new出来的对象
	return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值