浅析智能指针

来源:互联网 发布:mac版阿里旺旺退出账号 编辑:程序博客网 时间:2024/06/05 10:30

1、什么是RAII技术

       RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的简单计数。它定义了一个类来封装资源的分配和释放,在构造函数完成资源的分配和初

始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。这种做法有两大优点:

    (1)不需要显示的释放资源。

    (2)采用这种方式,对象所需的资源在其生命周期内始终保持有效。 

2、为什么需要智能指针

      代码中经常会忘记释放动态开辟的资源,这样就不知不觉的造成内存泄漏

3、什么是智能指针

     所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。

     智能指针是RAII的一种应用,他可以:

(1)构造函数初始化,析构函数释放资源

(2)像指针一样使用——operator*/operator->/operator[]

(3)对各种类型智能指针赋值

4、智能指针的发展与实现:

      当我们有了对象sp1,用它构造了sp2,有时我们希望它们拥有不同的内存空间,但有希望当其中一个值改变时另一个值也随即改变。此时深拷贝只帮我们实现了前半部分,

即一个对象一个内存空间,浅拷贝帮我们实现了后半部分,即改变其中一个值另一个值也随即改变,但它的内存释放有问题,因此我们需要另一种方法实现它。

(1)C++98/03 用的是Auto_ptr,它实现内存管理权的转移,这是一种有缺陷的方法;

代码示例:

#include<iostream>
#include<memory>
using namespace std;
template <class T>
class AutoPtr
{
public:
     AutoPtr(T* ptr)
            :_ptr(ptr)
      {}
    AutoPtr( AutoPtr<T>& a)
   {
         _ptr=a._ptr;

         _ptr=NULL;//管理权转移
   }

    AutoPtr<T>& operator=(const AutoPtr<T>& a)
   {
       if(this!=&a)
     {
        delete this->_ptr;
        this->_ptr=a._ptr;
        a._ptr=NULL;
    }
       return *this;
  }
   ~AutoPtr()
  {
       delete _ptr;

       cout<<"~AutoPtr()"<<endl;
   }
   T& operator*()
 {
       if(_ptr==NULL)
      {
        cout<<"空指针"<<endl;
     }
       return *_ptr;
 }
   T& operator->()
  {
       if(_ptr==NULL)
     {
         cout<<"空指针"<<endl;
    }
         return _ptr;
 }
 
private:
       T* _ptr;
};
int main()
{
    AutoPtr<int> sp1(new int(5));
    AutoPtr<int> sp2(sp1);
    *sp2=10;

    return 0;
}

内存展示:

 

 

        sp2是sp1拷贝构造的,会使sp2的对象指向sp1,即获得sp1的管理权,而sp1不再管理对象sp1,所以又造成上图所示的sp1的指针_ptr指向错误。

(2)介于Auto_ptr的缺陷,之后出现了scoped_ptr(boost库)和unique_ptr(C++11)。它们采用简单粗暴的方法—防拷贝,它将拷贝构造函数和赋值运算符重载函数只声明不定

义,并且声明成保护成员函数,这样就避免了管理权的转移,并防止了在类外定义这些函数。但也因此不能实现对象之间的拷贝和赋值,这也是ScopedPtr的不足之处。

测试代码如下:

#include<iostream>
#include<memory>
using namespace std;
template<class T>
class ScopedPtr
{
public:
 ScopedPtr(T* ptr)
  :_ptr(ptr)
 {}
 ~ScopedPtr()
 {
  delete _ptr;
  cout<<"~ScopedPtr()"<<endl;
 }
 T&operator*()
 {
  return *_ptr;
 }
 T&operator->()
 {
  return _ptr;
 }
protected:
 ScopedPtr(ScopedPtr<T>& s);//防拷贝(只声明,为防止在内外定义)
 ScopedPtr<T> operator=(ScopedPtr<T>& s);
private:
 T* _ptr;
};
void TestScopedPtr()
{
 ScopedPtr<int> sp1(new int(5));
 /*ScopedPtr<int> sp2(sp1);*/  //不能访问protected成员
}
int main()
{
 TestScopedPtr();
 return 0;
}

(3)由于以上两种方法的缺陷,Shared_Ptr(boost/C++11)应运而生。这是一种比较完善得方法,他保持了对象的共享拥有权。若干个Shared_Ptr对象可以共享同一个对象,该对象通过维护一个引用计数,记录有多少个Shared_Ptr指针指向该对象,最后一个指针指向该对象的Shared_Ptr将销毁或重置,即引用计数减为0.销毁对象时使用的引用计数是delete表达式或者是构造Shared_Ptr是传入的自定义删除器,但是Shared_Ptr指针同样有缺陷,那就是循环引用和现成安全问题,不过可以采用weak_ptr配合解决。

Shared_Ptr实现代码(实现引用计数):

 #include<iostream>
#include<memory>
using namespace std;
template<class T>
class Shared_Ptr
{
public:
 Shared_Ptr(T* ptr)
  :_ptr(ptr)
  ,_refcount(new int(1))
 {}
 Shared_Ptr()
  :_ptr(NULL)
  ,_refcount(new int(1))
 {}
 Shared_Ptr(const Shared_Ptr<T>& sp)
  :_ptr(sp._ptr)
  ,_refcount(sp._refcount)
 {
  (*_refcount)++;
 }
 Shared_Ptr<T>& operator=(const Shared_Ptr<T>&sp)
 {
  if(_ptr!=sp._ptr)
  {
   delete _ptr;
   delete _refcount;
   _ptr=sp._ptr;
   _refcount=sp._refcount;
   ++(*_refcount);
  }
  return *this;
 }
 ~Shared_Ptr()
 {
  Realease();
 }
 T& operator*()
 {
  return *_ptr;
 }
 T& operator->()
 {
  return _ptr;
 }
 T Getrefcount()
 {
  return*(_refcount);
 }
 inline void Realease()
 {
  if(--*_refcount==0)
  {
   delete _refcount;
   delete _ptr;
   
  }
 }
 void Reset(T* ptr,T* refcount)
 {
  if(_ptr!=ptr)
  {
   delete _ptr;
   delete _refcount;
  }
  _ptr=ptr;
  _refcount=refcount;
 }
public:
 T* _ptr;
 T* _refcount;
};
void test()
{
 Shared_Ptr<int> s1(new int(10));
 cout<<s1.Getrefcount()<<endl;
 Shared_Ptr<int> s2(s1);
 cout<<s2.Getrefcount()<<endl;
 Shared_Ptr<int> s3(new int(30));
 s3=s1;
 cout<<s3.Getrefcount()<<endl;
}
int main()
{
 test();
 return 0;
}

这种方法看似不错,但实际上存在以下问题:

(1)、引用计数更新存在线程安全;

(2)、循环引用;

(3)、定置删除器;

      下面我们将使用一个弱指针(weak_ptr)来打破循环引用。

 

#include<boost/shared_ptr.hpp>
#include<boost/weak_ptr.hpp>
using namespace std;
using namespace boost;
struct ListNode
{
 shared_ptr<ListNode> _prev;
 shared_ptr<ListNode> _next;

 /*weak_ptr<ListNode> _prev;
 weak_ptr<ListNode> _next;*/

 ~ListNode()
 {
  cout<<"~ListNode"<<endl;
 }
};
void test()
{
 //循环引用问题
 shared_ptr<ListNode>p1(new ListNode());
 shared_ptr<ListNode>p2(new ListNode());
 
 p1->_next=p2;//P1节点的_next指向p2节点
 p2->_prev=p1;//p2节点的_prev指向p1节点

 cout<<"p1->Count:"<<p1.use_count()<<endl;
 cout<<"p2->Count:"<<p2.use_count()<<endl;
}
int main()
{
 test();
 return 0;
}

运行结果:

  定置删除器和空间分配器:

class FClose
{
public:
 void operator() (void* ptr)
 {
  cout<<"fclose"<<endl;
  fclose((FILE*)ptr);
 }
};
class Free
{
public:
 void operator() (void* ptr)
 {
  cout<<"free"<<endl;
  free(ptr);
 }
};
void Test()
{
 shared_ptr<FILE>p1 (fopen("test.txt","w"),FClose());//打开一个文本文件再手动删除
 shared_ptr<int>p2 ((int*)malloc(sizeof(int)),Free(),allocator<int>());//手动申请空间并释放
}
int main()
{
 Test();
 return 0;
}

         定置删除器进行手动的开辟和删除,人为解决空间的开辟和释放问题。

5、智能指针总结:

        如果确定要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器中的对象,你应该使用std::tr1::shared_ptr,任何情况下都不要使用auto_ptr。

         "智能"指针看上去是指针,其实是附加了语义的对象。以scoped_ptr为例,scoped_ptr被销毁时,删除了他所指向的对象。shared_ptr也是如此,而且,shared_ptr实现了

引用计数,从而只有当它所指向的最后一个对象被销毁时,指针才被删除。

0 0
原创粉丝点击