目录
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_ptr | weak_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;
}