引言:C++中的内存泄露问题一直是一个困扰很多人的问题,当然现在也有很多检测泄露的工具,但是我们还是希望有一套比较方便使用的机制,能够保证申请的内存能够及时的释放掉,做到这一点应该是大家都期望的事情。
搞C++的对于动态内存的处理相信都有一个基本的原则就是“谁申请谁释放原则”,如果内存使用的周期比较短函数调用结束我们就可以释放掉的话我们就不用担心泄露的问题了,但是往往我们是在函数new一块内存而将该指针返回,在这种情况下就需要在函数外部对内存就行释放了,记性好还行但是如果指针又被传递了,往往我们就会忘记释放了,这时候如果能够按照“谁申请谁释放原则”是不是就不必担心忘记释放内存了呢,但是如何才能做到函数内new一块内存,在其他地方delete,而又保证“谁申请谁释放”的原则呢?
我们一步一步来,先看下标准库给我们提供的auto_ptr 这个类
首先是构造函数,普通构造函数和拷贝构造函数
1、普通构造函数
template<class _Ty>
class auto_ptr
{
public:
//... 后面实现
private:
_Ty* _Myptr;
}
auto_ptr(_Ty* _Ptr=0):_Myptr(_Ptr)
{
}
2、拷贝构造函数
auto_ptr(auto_ptr<_Ty>& _Right)
: _Myptr(_Right.release())
{
}
3、releas函数释放掉对指针的控制权
_Ty *release()
{ // return wrapped pointer and give up ownership
_Ty *_Tmp = _Myptr;
_Myptr = 0;
return (_Tmp);
}
4、少不了operator=操作符重载
auto_ptr<_Ty>& operator=(auto_ptr<_Ty>& _Right)
{
reset(_Right.release());
return (*this);
}
5、reset函数释放掉原来的指针
void reset(_Ty* _Ptr = 0)
{ // destroy designated object and store new pointer
if (_Ptr != _Myptr)
delete _Myptr;
_Myptr = _Ptr;
}
6、析构函数
~auto_ptr()
{ // destroy the object
delete _Myptr;
}
由上面几个函数可以看出auto_ptr仅仅是将指针封装在类中,通过局部对象在作用域结束后调用析构函数来做到自动释放内存。
</pre><pre class="cpp" name="code">int main()
{
int *pN = new int(20);
auto_ptr ptr(pN);
int n = *ptr;
}
为方便用指针的方式访问auto_ptr,所以需要重载* 和->操作符
_Ty& operator*() const _THROW0()
{ // return designated value
return (*_Myptr);
}
_Ty *operator->() const _THROW0()
{ // return pointer to class object
return (&**this);
}
由此看来auto_ptr做到了函数内部的自动释放,而且,当将auto_ptr对象返回的话将控制权也传递出去了,最后的auto_ptr释放时指针也会被释放掉,防止了内存泄露基本满足“谁申请谁释放原则”了。貌似可以很好了吧,但这里有一个小缺陷,也就是在拷贝构造及=赋值时原来的auto_ptr对指针的控制权被释放掉了,也就是其指向的地址为空了,同一时刻只能有一个auto_ptr能操作赋给他的指针。当然这在我们看来还不完美。如何才能做到即满足“谁申请谁释放”原则,当传递控制权后还保留对指针的访问权呢?boost库位我们提供了share_ptr这个类。使用share_ptr需要包含boost头文件,使用boost库文件。
#include <boost/shared_ptr.hpp>
using namespace boost;
从名字上我们可以猜测到对指针的访问权是共享的,如何才能做到共享呢,这时候引入一个引用计数的概念。我们在windows核心编程中句柄与内核对象的关系式貌似接触过他,可以从这点加深其的使用方式。
首先还是源码分析
先看下类的定义
template <class T>
class share_ptr
{
T * px; // contained pointer
boost::detail::shared_count pn; // reference counter
}
1、普通构造
shared_ptr(): px(0), pn() // 默认构造函数
{
}
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn( p ) // 用指针初始化
{
boost::detail::sp_enable_shared_from_this( this, p, p ); //这一句可以忽略有些特别的处理而已
}
2、拷贝构造
shared_ptr( shared_ptr<Y> const & r )
: px( r.px ), pn( r.pn ) // never throws
{
}
3、重载=
shared_ptr & operator=( shared_ptr const & r ) // never throws
{
this_type(r).swap(*this);
return *this;
}
void swap(shared_ptr<T> & other) // never throws
{
std::swap(px, other.px);
pn.swap(other.pn);
}
share_ptr有两个成员变量
shared_count pn<pre class="cpp" name="code"> T * px;
其中px为其保存的指针,pn为引用计数所在对象有引入另一个类share_count
class shared_count
{
private:
sp_counted_base * pi_;
}
shared_count(): pi_(0)
{
}
template<class Y> explicit shared_count( Y * p ): pi_( 0 )
{
pi_ = new sp_counted_impl_p<Y>( p );
if( pi_ == 0 )
{
boost::checked_delete( p );
boost::throw_exception( std::bad_alloc() );
}
}
class sp_counted_base
{
long use_count_;
}
sp_counted_base(): use_count_( 1 ), weak_count_( 1 )
{
}
virtual ~sp_counted_base() // nothrow
{
}
void add_ref_copy()
{
BOOST_INTERLOCKED_INCREMENT( &use_count_ );
}
sp_counted_impl_p 是 sp_counted_base的一个子类,当share_ptr初始化一个空指针时是没有引用计数的,但当初始化一个实际指针时引用计数调用的是上面的函数,将指针传递给share_count,并最后传给sp_counted_base
2、拷贝构造
shared_count(shared_count const & r): pi_(r.pi_) // nothrow
{
if( pi_ != 0 ) pi_->add_ref_copy();
}
3、重载=
shared_count & operator= (shared_count const & r) // nothrow
{
sp_counted_base * tmp = r.pi_;
if( tmp != pi_ )
{
if( tmp != 0 ) tmp->add_ref_copy();
if( pi_ != 0 ) pi_->release();
pi_ = tmp;
}
return *this;
}
两个函数中都有add_ref_copy(),这是对引用计数加一,也就是说没当拷贝构造或者=赋值操作时指针的引用计数都会加一,而pi_->release()做了什么呢?
void release() // nothrow
{
if( BOOST_INTERLOCKED_DECREMENT( &use_count_ ) == 0 )
{
dispose() //函数在sp_counted_impl_p中被重载 调用boost::checked_delete( px_ );也就是删除传递过来的指针
}
}
release对引用计数减一,当为0时就将指针释放掉。这样我们就做到了对指针的共享,并且当所有share_ptr析构时指针会被顺利的释放
int main()
{
int *n=new int(20);
share_ptr pA(n); //初始化引用计数为1
share_ptr pB;
pB=pA; //=赋值传递了一次引用计数加1,现在是2
cout<<*pA; //可以访问是有值的auto_ptr是无值的。
cout<<*pB;
}
pA pB都是对象所以pA析构时引用计数减1,只有当pB也析构时引用计数减到0了n才会被释放。我们不需要考虑n被传递多少次了。
小问题: 这个时候我们要问了引用计数存在sp_counted_base中为什么还要加一个share_count呢?
我们使用sp_counted_base时使用的是指针这是在构造share_count时
if( pi_ != 0 ) pi_->add_ref_copy();需要判断传进来的是不是空指针,而且引用计数也是动态申请内存保存的,所以需要用指针。
如果sp_counted_base直接保存在share_ptr中,因为sp_counted_base是指针所以只是将占用的地址释放掉而已没有调用任何操作,
这说明share_ptr中需要的是一个对象在share_ptr被析构时对象调用析构函数处理删除操作,所以share_count被加入到中间来了。
当share_ptr被析构时,share_count同样被析构,调用share_count的析构函数,让sp_counted_base调用响应的处理(引用计数减1)
补充:
永不建立auto_ptr的容器
关于此可以看的Effective STL的条款8
因为auto_ptr并不是完美无缺的,它的确很方便,但也有缺陷,在使用时要注意避免。首先,不要将auto_ptr对象作为STL容器的元素。C++标准明确禁止这样做,否则可能会碰到不可预见的结果
auto_ptr的另一个缺陷是将数组作为auto_ptr的参数: auto_ptr<char> pstr (new char[12] ); //数组;为定义
然后释放资源的时候不知道到底是利用delete pstr,还是 delete[] pstr;
然后收集了关于auto_ptr的几种注意事项:
1、auto_ptr不能共享所有权。
2、auto_ptr不能指向数组
3、auto_ptr不能作为容器的成员。
4、不能通过赋值操作来初始化auto_ptr
std::auto_ptr<int> p(new int(42)); //OK
std::auto_ptr<int> p = new int(42); //ERROR
这是因为auto_ptr 的构造函数被定义为了explicit
5、不要把auto_ptr放入容器
1. shared_ptr是Boost库所提供的一个智能指针的实现,shared_ptr就是为了解决auto_ptr在对象所有权上的局限性(auto_ptr是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针.
2. shared_ptr比auto_ptr更安全
3. shared_ptr是可以拷贝和赋值的,拷贝行为也是等价的,并且可以被比较,这意味这它可被放入标准库的一般容器(vector,list)和关联容器中(map)。