C++ -- 智能指针( C++11与boost库的智能指针及其使用)

智能指针的引入

1.在动态内存管理中,如果new上一块空间,但是没有delete,就会产生内存泄露的问题。
2.但是有时候,我们new了,也delete了,但是还会出现问题。例如在new和delete之间调用了某个抛异常的函数,就有可能导致没有执行delete。
例如:fun2里面使用了new[],最后也使用了delete[],看着没有问题,但是在new和delete之间调用了fun1,而fun1里面抛了异常,但是在fun2的delete之前并没有捕获,就会导致delete没有执行,仍然会有内存泄露的问题。

void fun1()
{
      throw int(11);
}
void fun2()
{
      int* p = new int[10000];
      fun1();
      delete[] p;
}

int main()
{
      try
      {
           fun2();
      }
      catch (int& e)
      {
           cout << "捕获" << endl;
      }
      system("pause");
      return 0;
}

如果想要解决上面的问题,有一种方法:就是如果发现delete之前发现某个函数抛出了异常,就在delete之前捕获这个异常,并且在catch语句里面进行资源的释放,并且可以再将这个异常重新抛出。

void fun2()
{
      int* p = new int[10000];
      try
      {
           fun1();
      }
      catch(int& e)
      {
           delete[] p;
           cout << "重新抛出" << endl;
           throw;
      }
      delete[] p;
}

但是这种方法写着比较繁琐,而且如果代码很多,就有可能忘记哪个函数抛出了异常,会防不胜防。
3.因此,只要有一种方法,能够在出了作用域之后能够自动释放掉申请的空间,就会让空间得到正确的释放。而一个对象出了作用域会自动调用自己的析构函数,只要在析构函数里能够释放自己开辟的空间,就能达到目的。
4.智能指针就有上面的作用,能够自动的管理指针所指向的动态资源的释放。它不仅有着RAII的思想还能够像指针一样。(RAII:分配资源即初始化,即构造函数分配资源和初始化资源,在析构函数清理资源。像指针一样:能够解引用)。
5.智能指针实质上就是一个模板类,成员变量包含一个任意类型的指针,构造函数获得资源并初始化,析构函数清理资源。
注意:智能指针只能管理动态开辟的空间。

智能指针的发展史

  • 智能指针都具有RAII的思想,即构造函数获得资源,析构函数清理资源,但是当用一个智能指针拷贝构造另一个智能指针的时候,有可能会有浅拷贝的问题,这个空间会被释放多次,智能指针的发展就是围绕着指针拷贝问题而走。
1.auto_ptr

(1)C++98里面有一个智能指针auto_ptr,对于拷贝构造和赋值运算符重载,该智能指针采用管理权转移的方式(当一个指针拷贝构造另一个指针时,当前指针就将对空间的管理权交给拷贝的那个指针,当前指针就指向空);
(2)但是这种方式不符合指针的要求(可以允许多个指针指向同一块空间,将一个指针赋值给另一个指针的时候,就是需要让两个指针指向同一块空间,而auto_ptr只允许一块空间上只能有一个指针指向它),并且当管理权转移之后要想再访问之前的指针,就会出错,因为之前的指针已经为NULL,就会出现解引用空指针的问题。

2.scoped_ptr/shared_ptr

因为auto_ptr有缺陷,但是C++标准里面从C++98到C++11之间没有出现新的智能指针能解决这个缺陷,所以在这段时间内,boost这个官方组织就增加了智能指针(scoped_ptr,shared_ptr,weak_ptr等)
(1)scoped_ptr采用防拷贝的方式(防拷贝就是不允许拷贝,拷贝就会出错;防拷贝的实现:将拷贝构造和的赋值运算符重载只声明不实现,并且声明为私有);
(2)shared_ptr为共享指针,里面采用引用计数,当有shared_ptr指向同一块空间的时候就增加引用计数,当引用计数减为0的时候才释放该智能指针管理的那块空间。
(3)但是shared_ptr有一个缺点,就是会出现循环引用的问题(当一个shared_ptr(如sp1)管理的空间里面包含一个shared_ptr的指针(_next),另一个shared_ptr(如sp2)管理的空间里面也包含一个shared_ptr指针(_prev)时,当sp1->_next = sp2;sp2->_prev = sp1;此时就会使得sp1和sp2的引用计数都变为2,当出了这个作用域sp1和sp2的引用计数都会减为1,但是只有引用计数为0时才会释放管理的空间,就会使得sp1和sp2管理的空间没有释放。
(4)所以利用weak_ptr来解决循环引用的问题,weak_ptr叫弱指针,它主要是为了配合shared_ptr使用,用来解决循环引用的问题;

  • 将会出现循环引用问题的指针用weak_ptr保存着,weak_ptr并不拥有这块空间,所以weak_ptr里面不增加shared_ptr的引用计数,也不会释放这块空间。(注意weak_ptr里面也有自己的引用计数)

(5)boost库里面还包含scoped_array和shared_array(这个适用于delete[]的场景)

3.C++11(unique_ptr和shared_ptr)

(1)C++11借鉴了boost库里面的智能指针(C++对应的智能指针位于头文件<memory>里。

  • C++11里面的unique_ptr就是boost库里面的scoped_ptr(防拷贝);
  • C++11里面的shared_ptr就是boost里面的shared_ptr。

(2)C++11里面不包含类似于scoped_array和shared_array,而它采用定制删除器的方式管理空间的释放。

  • 定制删除器就是自己指定采用何种方式释放该空间(delete/free或其它);
  • 因为在实现智能指针的过程中,我们需要管理数据的构造和析构,但不同的数据有不同的析构方式,就需要自己指定删除方式。(例如new出来的数据,就必须用delete,而new[ ]就需要delete[ ]等)。注意:boost库里面也包含定制删除器。

C++98里面的auto_ptr

1.源代码分析:

   template<class _Ty>
   class auto_ptr
   {  
   public:
      typedef auto_ptr<_Ty> _Myt;
      typedef _Ty element_type;

        //构造函数
      explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()
           : _Myptr(_Ptr)
           {}

        //拷贝构造函数,release返回保存_Right指向空间的临时变量,则_Myptr管理_Right管理的空间,并且将_Right置为空
      auto_ptr(_Myt& _Right) _THROW0()
           : _Myptr(_Right.release())
           {}

      //赋值运算符重载,_Right.release()将_Right的管理权转移给_Myptr,将_Right置为空。reset里面判断是不是自己给自己赋值,不是就释放_Myptr,并且让_Myptr管理_Right以前管理的个指针,再返回*this。
      _Myt& operator=(_Myt& _Right) _THROW0()
           {  
           reset(_Right.release());
           return (*this);
           }

     //析构函数释放_myptr
      ~auto_ptr() _NOEXCEPT
           {    
           delete _Myptr;
           }

      _Ty& operator*() const _THROW0()
           {  
 #if _ITERATOR_DEBUG_LEVEL == 2
           if (_Myptr == 0)
                 _DEBUG_ERROR("auto_ptr not dereferencable");
 #endif 

           return (*get());
           }

      _Ty *operator->() const _THROW0()
           {    
 #if _ITERATOR_DEBUG_LEVEL == 2
           if (_Myptr == 0)
                 _DEBUG_ERROR("auto_ptr not dereferencable");
 #endif
           return (get());
           }

       //get返回原生指针
      _Ty *get() const _THROW0()
           {     
           return (_Myptr);
           }

       //release用于管理权的转移,将_myptr保存在tmp里面,将_mytmp的指针置为空,再返回tmp
      _Ty *release() _THROW0()
           {     // return wrapped pointer and give up ownership
           _Ty *_Tmp = _Myptr;
           _Myptr = 0;
           return (_Tmp);
           }

      void reset(_Ty *_Ptr = 0)
           {   
           if (_Ptr != _Myptr)
                 delete _Myptr;
           _Myptr = _Ptr;
           }

private:
      _Ty *_Myptr;     // the wrapped object pointer
      };

2.使用:(必须包含头文件<memory>)
将动态开辟的指针交给一个智能指针。

void Test_auto_ptr()
{
      auto_ptr<int> ap1(new int(10));
      cout << *ap1 << endl;  //输出10

      auto_ptr<int> ap2(ap1);
      cout << *ap2 << endl;   //输出10
      cout << *ap1 << endl;   //此时ap1为NULL,就是解引用空指针,程序崩溃
}

boost里面的scoped_ptr

1.源代码:

template<class T> 
class scoped_ptr 
{
private:

    T * px;    //只含有一个成员变量T*的指针

     //将拷贝构造与赋值运算符重载声明为私有,且不实现
    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 ) 
    {
#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 ) BOOST_NOEXCEPT 
         : px( p.release() )
    {
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
        boost::sp_scalar_constructor_hook( px );
#endif
    }

#endif

     //析构函数
    ~scoped_ptr() 
    {
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
        boost::sp_scalar_destructor_hook( px );
#endif
        boost::checked_delete( px );
    }

    void reset(T * p = 0) 
    {
        BOOST_ASSERT( p == 0 || p != px ); 
        this_type(p).swap(*this);
    }

    T & operator*() const 
    {
        BOOST_ASSERT( px != 0 );
        return *px;
    }

    T * operator->() const 
    {
        BOOST_ASSERT( px != 0 );
        return px;
    }

     //获得原生指针
    T * get() const BOOST_NOEXCEPT
    {
        return px;
    }

#include <boost/smart_ptr/detail/operator_bool.hpp>

    void swap(scoped_ptr & b) BOOST_NOEXCEPT
    {
        T * tmp = b.px;
        b.px = px;
        px = tmp;
    }

2.使用:
当使用boost库的时候,必须先要从boost官方网站下载boost的源码,然后将从boost库下载的源代码所在目录包含至自己的项目,并且使用时要指定命名空间为boost。

#include<iostream>
#include<string>
#include<boost/scoped_ptr.hpp>
#include<boost/scoped_array.hpp>

int main()
{
      boost::scoped_ptr<int> sp1(new int(10));

      //boost::scoped_ptr<int> sp2(sp1);     //不能拷贝

      boost::scoped_array<std::string> sp2(new std::string[10]);

      return 0;
}

boost库shared_ptr的使用

1.shared_ptr里面采用引用计数来管理空间,由于shared_ptr一共用了6个模块来实现,所以这里就不剖析源代码。
2.使用:

void Test_boost_shared_ptr()
{
      boost::shared_ptr<int> sp1(new int(10));
      std::cout << *sp1 << std::endl;    //输出10

      boost::shared_ptr<int> sp2(sp1);
      std::cout << *sp2 << std::endl;    //输出10

      boost::shared_array<std::string> sp3(new std::string[10]);
      sp3[5] = "111";     //shared_array里面重载了[],所以可以采用下标的方式进行读写
      boost::shared_array<std::string> sp4(sp3);
      std::cout << sp4[5] << std::endl;   //输出111
}

C++11里shared_ptr的使用

1.基本使用与boost里shared_ptr的使用方式一致

void Test_Shared_ptr()
{
      std::shared_ptr<int> sp1(new int(20));
      std::shared_ptr<int> sp2(sp1);
      std::cout << *sp2 << std::endl;
}

2.由于C++11里面没有shared_array或scoped_array之类智能指针,所以只能用shared_ptr需要自己定制删除器:
(1)首先自己定制删除器(例如我定制了一个delete和一个delete[ ])
自己需要编写相应的仿函数,在用shared_ptr时不用将自己编写的删除器作为模板参数,但是在构造shared_ptr对象时需要传相应仿函数的对象。

template<class T>
struct Delete
{
      void operator()(T* ptr)
      {
           delete ptr;
      }
};

template<class T>
struct DeleteArray
{
      void operator()(T* ptr)
      {
           delete[] ptr;
      }
};

(2)使用:

void Test_Shared_ptr()
{
      std::shared_ptr<int> sp1(new int(20));
      std::shared_ptr<int> sp2(sp1);
      std::cout << *sp2 << std::endl;

      DeleteArray<std::string> da;
      std::shared_ptr<std::string> sp3(new std::string[10],da); 

}

猜你喜欢

转载自blog.csdn.net/xu1105775448/article/details/80625936