智能指针用于指向存放在堆,即动态分配对象指针的类,用于对指针所指向的对象生存期的控制,防止内存泄露。
boost库中的智能指针定义在namespace boost中,包括:
shared_ptr
weak_ptr
scoped_ptr
shared_arr
scoped_arr
auto_ptr
auto_ ptr是在std库中的,不在boost库中,但是auto_ptr能够说清楚智能指针的原理,所以在这里说一下。
auto_ptr用于指向一个动态分配的对象指针,他的析构函数用于删除所值对象的空间,以此达到对对象生存期的控制。
auto_ ptr在进行拷贝构造和赋值的时候,会对控制权进行转移,即当另一个auto_ ptr要指向一个已经被另一个auto_ ptr控制的对象的时候,以前那个auto_ ptr必须释放控制权。
就像下面这样:
原因:
如果不这样做,在析构的时候就会对同一段内存空间析构两次,造成错误。
但是仅仅对拷贝构造和赋值进行限制不能完全保证没有两个智能指针指向同一个对象,所以auto_ptr是不好的。
下面是我自己实现auto_ptr的代码:
template <typename T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
{}
AutoPtr(const AutoPtr& a)
{
_ptr = a._ptr;
a._ptr = NULL;
}
AutoPtr& operator = (const AutoPtr& a)
{
if (this != &a)
{
_ptr = a._ptr;
a._ptr = NULL;
}
return *this;
}
~AutoPtr()
{
delete _ptr;
}
T& operator*()
{
if (_ptr == NULL)
{
throw a;
}
return *_ptr;
}
T* operator ->()const
{
if (_ptr == NULL)
{
throw a;
}
return _ptr;
}
bool operator == (const AutoPtr& a)
{
return (_ptr == a._ptr);
}
bool operator !=(const AutoPtr& a)
{
return (_ptr != a._ptr);
}
void Reset(T* ptr = NULL)
{
if (_ptr != ptr)
{
delete _ptr;
}
_ptr = ptr;
}
private:
T* _ptr;
};
scoped_ptr
auto_ ptr在使用的时候,会有控制权的转移。其实scoped_ ptr的原理与auto_ ptr是相同的,只不过scoped_ ptr完全不允许控制权的转移,即将拷贝构造函数和赋值运算符重载函数都声明为私有的了,外部不允许调用。
下面的代码是boost库中scoped_ptr的代码:
代码中的explict关键字表示不允许隐式调用构造函数,保证了不转移控制权。
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 ): px( p ) // never throws
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px );
#endif
}
#ifndef BOOST_NO_AUTO_PTR
explicit scoped_ptr( std::auto_ptr<T> p ): px( p.release() ) // never throws
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px );
#endif
}
#endif
~scoped_ptr() // never throws
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_destructor_hook( px );
#endif
boost::checked_delete( px );
}
void reset(T * p = 0) // never throws
{
BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
this_type(p).swap(*this);
}
T & operator*() const // never throws
{
BOOST_ASSERT( px != 0 );
return *px;
}
T * operator->() const // never throws
{
BOOST_ASSERT( px != 0 );
return px;
}
T * get() const // never throws
{
return px;
}
// implicit conversion to "bool"
#include <boost/smart_ptr/detail/operator_bool.hpp>
void swap(scoped_ptr & b) // never throws
{
T * tmp = b.px;
b.px = px;
px = tmp;
}
};
shared_ptr
在实际应用中,我们往往需要复制智能指针,上面的auto_ptr和scoped_ptr都不能满足我们的需求。
所以出现了shared_ptr,shared_ptr是采用引用计数的方法防止多次析构。
我们为每一个指针都维护一个引用计数,一旦多了一个智能指针指向那个内存的时候,引用计数加一,少了一个智能指针指向对象时,引用计数减一。
在析构的时候,每析构一次,引用计数减一,直到引用计数为0的时候,再析构对象及引用计数所占用的空间。
原理如下:
下面是我的代码模拟实现:
template <typename T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr)
, _ref(new int(0))
{
if (_ptr != NULL)
(*_ref)++;
}
SharedPtr(const SharedPtr&a)
{
_ptr = a._ptr;
_ref = a._ref;
if (_ptr != NULL)
(*_ref)++;
}
~SharedPtr()
{
if (--(*_ref) == 0)
{
delete _ptr;
delete _ref;
}
}
SharedPtr& operator=(const SharedPtr &a)
{
if (this != &a)
{
if (--(*_ref) <= 0)
{
delete _ptr;
delete _ref;
}
else{}
_ptr = a._ptr;
_ref = a._ref;
(*_ref)++;
}
return *this;
}
T operator*()
{
return *_ptr;
}
T* operator ->()
{
return _ptr;
}
bool operator == (const SharedPtr& a)
{
return (_ptr == a._ptr);
}
bool operator !=(const SharedPtr& a)
{
return (_ptr != a._ptr);
}
private:
T* _ptr;
int* _ref;
};
线程不安全
因为使用引用计数值为判定指标,所以在多线程的环境下,是线程不安全的,会因线程调用的先后顺序产生错误结果。
解决方法:
使用锁,对引用计数操作进行加锁,保证操作是互斥的。
循环引用问题
因为shared_ptr是通过引用计数进行生存期控制的,所以会出现循环引用问题。
我现在用下面的代码来解释循环引用的问题
class B;
class A
{
public:// 为了省去一些步骤这里 数据成员也声明为public
shared_ptr<B> pb;
void doSomthing()
{
}
~A()
{
cout << "~A";
}
};
class B
{
public:
shared_ptr<A> pa;
~B()
{
cout <<"~B";
}
};
int main(int argc, char** argv)
{
shared_ptr<A> sa(new A());
shared_ptr<B> sb(new B());
if(sa && sb)
{
sa->pb=sb;
sb->pa=sa;
}
cout<<"sa use count:"<<sa.use_count()<<endl;
return 0;
}
一旦运行上面的代码,运行结果为:sa use count:2, 注意此时sa,sb都没有释放,产生了内存泄露问题
即A内部有指向B,B内部有指向A,这样对于A,B必定是在A析构后B才析构,对于B,A必定是在B析构后才析构A,这就是循环引用问题,违反常规,导致内存泄露。
循环引用出现的场景:
如二叉树中父节点与子节点的循环引用,容器与元素之间的循环引用等。
weak_ptr
解决上面循环引用的一个方法就是使用weak_ptr,weak_ptr是结合shared_ptr使用的智能指针。
weak_ ptr允许对一个或多个shared_ptr所指向的对象进行访问,但是不对引用计数值进行改变,即观察对象,但不对对象进行改变。
weak_ ptr的实现没有实现->与*运算符的重载,所以不能直接使用weak_ ptr访问对象。
boost库提供了expired()与lock()成员函数,前者用于判断weak_ ptr指向的对象是否已被销毁,后者返回其所指对象的shared_ ptr智能指针(对象销毁时返回”空“shared_ptr)。
boost库中weak_ptr的实现:
template<class T> class weak_ptr
{
private:
// Borland 5.5.1 specific workarounds
typedef weak_ptr<T> this_type;
public:
typedef T element_type;
weak_ptr(): px(0), pn() // never throws in 1.30+
{
}
template<class Y>
#if !defined( BOOST_SP_NO_SP_CONVERTIBLE )
weak_ptr( weak_ptr<Y> const & r, typename boost::detail::sp_enable_if_convertible<Y,T>::type = boost::detail::sp_empty() )
#else
weak_ptr( weak_ptr<Y> const & r )
#endif
: px(r.lock().get()), pn(r.pn) // never throws
{
}
#if defined( BOOST_HAS_RVALUE_REFS )
template<class Y>
#if !defined( BOOST_SP_NO_SP_CONVERTIBLE )
weak_ptr( weak_ptr<Y> && r, typename boost::detail::sp_enable_if_convertible<Y,T>::type = boost::detail::sp_empty() )
#else
weak_ptr( weak_ptr<Y> && r )
#endif
: px( r.lock().get() ), pn( static_cast< boost::detail::weak_count && >( r.pn ) ) // never throws
{
r.px = 0;
}
// for better efficiency in the T == Y case
weak_ptr( weak_ptr && r ): px( r.px ), pn( static_cast< boost::detail::weak_count && >( r.pn ) ) // never throws
{
r.px = 0;
}
// for better efficiency in the T == Y case
weak_ptr & operator=( weak_ptr && r ) // never throws
{
this_type( static_cast< weak_ptr && >( r ) ).swap( *this );
return *this;
}
#endif
template<class Y>
#if !defined( BOOST_SP_NO_SP_CONVERTIBLE )
weak_ptr( shared_ptr<Y> const & r, typename boost::detail::sp_enable_if_convertible<Y,T>::type = boost::detail::sp_empty() )
#else
weak_ptr( shared_ptr<Y> const & r )
#endif
: px( r.px ), pn( r.pn ) // never throws
{
}
#if !defined(BOOST_MSVC) || (BOOST_MSVC >= 1300)
template<class Y>
weak_ptr & operator=(weak_ptr<Y> const & r) // never throws
{
px = r.lock().get();
pn = r.pn;
return *this;
}
#if defined( BOOST_HAS_RVALUE_REFS )
template<class Y>
weak_ptr & operator=( weak_ptr<Y> && r )
{
this_type( static_cast< weak_ptr<Y> && >( r ) ).swap( *this );
return *this;
}
#endif
template<class Y>
weak_ptr & operator=(shared_ptr<Y> const & r) // never throws
{
px = r.px;
pn = r.pn;
return *this;
}
#endif
shared_ptr<T> lock() const // never throws
{
return shared_ptr<element_type>( *this, boost::detail::sp_nothrow_tag() );
}
long use_count() const // never throws
{
return pn.use_count();
}
bool expired() const // never throws
{
return pn.use_count() == 0;
}
bool _empty() const // extension, not in std::weak_ptr
{
return pn.empty();
}
void reset() // never throws in 1.30+
{
this_type().swap(*this);
}
void swap(this_type & other) // never throws
{
std::swap(px, other.px);
pn.swap(other.pn);
}
void _internal_assign(T * px2, boost::detail::shared_count const & pn2)
{
px = px2;
pn = pn2;
}
template<class Y> bool _internal_less(weak_ptr<Y> const & rhs) const
{
return pn < rhs.pn;
}
// Tasteless as this may seem, making all members public allows member templates
// to work in the absence of member template friends. (Matthew Langston)
#ifndef BOOST_NO_MEMBER_TEMPLATE_FRIENDS
private:
template<class Y> friend class weak_ptr;
template<class Y> friend class shared_ptr;
#endif
T * px; // contained pointer
boost::detail::weak_count pn; // reference counter
};
强引用和弱引用
一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。
share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。
弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
使用weak_ptr来打破循环引用
代码如下:
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:// 为了省去一些步骤这里 数据成员也声明为public
weak_ptr<B> pb;
void doSomthing()
{
if(pb.lock())
{
}
}
~A()
{
cout << "kill A\n";
}
};
class B
{
public:
shared_ptr<A> pa;
~B()
{
cout <<"kill B\n";
}
};
int main(int argc, char** argv)
{
shared_ptr<A> sa(new A());
shared_ptr<B> sb(new B());
if(sa && sb)
{
sa->pb=sb;
sb->pa=sa;
}
cout<<"sb use count:"<<sb.use_count()<<endl;
return 0;
}
对数组进行管理
因为智能指针的析构函数中释放内存所用的都是delete运算符,只能释放单独的对象,不能对数组对象进行释放,所以我们必须定义一个arr智能指针,用于对数组进行管理。
下面给出两个我自己实现的数组智能指针
auto_arr
template <typename T>
class AutoArry
{
public:
AutoArry(T* ptr = NULL)
:_ptr(ptr)
{}
~AutoArry()
{
delete[] _ptr;
}
AutoArry(const AutoArry& a)
{
_ptr = a._ptr;
a._ptr = NULL;
}
AutoArry& operator = (const AutoArry& a)
{
if (this != &a)
{
_ptr = a._ptr;
a._ptr = NULL;
}
return *this;
}
T& operator*()
{
if (_ptr == NULL)
{
throw a;
}
return *_ptr;
}
T* operator->()
{
if (_ptr == NULL)
{
throw a;
}
return _ptr;
}
T& operator[](size_t index)
{
if (_ptr == NULL)
{
throw a;
}
return *(_ptr + index);
}
T& operator[](size_t index)const
{
if (_ptr == NULL)
{
throw a;
}
return *(_ptr + index );
}
void set(T* ptr)
{
int i = 0;
while (*(ptr+i))
{
*(_ptr + i) = *(ptr+i);
i++;
}
}
private:
T* _ptr;
};
shared_arr
对数组进行管理,依旧是使用引用计数。
template <typename T>
class SharedArr
{
public:
SharedArr(T* ptr = NULL)
:_ptr(ptr)
, _ref(new int(0))
{
if (_ptr != NULL)
{
(*_ref)++;
}
}
SharedArr(const SharedArr& a)
{
_ptr = a._ptr;
(*_ref)++;
}
~SharedArr()
{
if (--(*_ref) == 0)
{
delete[] _ptr;
delete _ref;
}
}
SharedArr& operator=(SharedArr& a)
{
if (this != &a)
{
if (--(*_ref) <= 0)
{
if (_ptr != NULL)
delete[] _ptr;
delete _ref;
}
else {}
_ptr = a._ptr;
_ref = a._ref;
(*_ref)++;
}
return *this;
}
T& operator[](size_t index)
{
if (_ptr == NULL)
{
throw a;
}
return *(_ptr + index);
}
T& operator[](size_t index) const
{
if (_ptr == NULL)
{
throw a;
}
return *(_ptr + index);
}
T& operator*( )
{
if (_ptr == NULL)
{
throw a;
}
return (*_ptr);
}
T* operator->()
{
if (_ptr == NULL)
{
throw a;
}
return _ptr;
}
private:
T* _ptr;
int* _ref;
};
定置删除器
经过上面的分析,我们都可以看到智能指针可以实现对内存的管理,简单的实现了垃圾回收机制,但是,上面所说的智能指针都不能用于文件。因为他的析构函数使用的是delete运算符。
所以我们使用仿函数以及函数指针构成定置删除器。
代码如下:
//定置删除器的仿函数
struct Fclose
{
void operator()(void *ptr)
{
fclose((FILE *)ptr);
cout << "fclose()" << endl;
}
};
void test()
{
boost::shared_ptr<FILE> sp(fopen("test.txt","w"),Fclose()); //调用构造函数构造一个匿名对象传递过去,文件正常关闭
}
//2.用智能指针管理malloc开辟的动态内存,那么我们在释放的时候就要用free释放:
//定置删除器的仿函数
struct Free
{
void operator()(void *ptr)
{
free(ptr);
}
};
void test()
{
boost::shared_ptr<int> sp((int*)malloc(sizeof(int)),Free()); //能够正确的释放空间
}