C++ Boost智能指针(smart_ptr)快速指南

智能指针(smart_ptr)是Boost各组件中,应用最为广泛的一个。

重所周知,C++没有提供Java中的垃圾回收机制。因此,在堆上申请的内存,需要自行回收,这就很容易导致内存泄漏。虽然STL提供了auto_ptr,但是受限太多(例如,不能放到容器中。。。),因此很少有人使用。

Boost从很早就提供了如下的智能指针,并且功能一直保持稳定:

  • scoped_ptr:不可拷贝,承载new。
  • scoped_array:不可拷贝,承载new []。
  • shared_ptr:可拷贝,承载new。
  • shared_array:可拷贝,承载new []。
  • weak_ptr:弱引用。
  • intrusive_ptr:需要自实现计数功能的,引用计数智能指针。

在阅读下文之前,还是要声明一点,有任何问题,请查阅官方文档:

http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/smart_ptr.htm

下面逐一进行介绍:

scoped_ptr

从名字就可以看出,这种智能指针只限于作用域内使用,无法转移内置指针的管理权(不支持拷贝、=赋值等)

但是作用也很显然,例如:

void test()
{
    int* p = new int(3);
    ....
    delete p;
}

假设....之中发生了异常,那么p就无法被delete,造成了内存泄漏。

使用scoped_ptr就可以很好解决这个问题,只需要new的时候放到scoped_ptr之中就可以了。

#include <iostream>
#include <boost/smart_ptr.hpp>

class SmallClass
{
    public:
        SmallClass(int x_val)
        {
            x = x_val;
            std::cout << "SmallClass construct " << x << std::endl;
        }

        virtual ~SmallClass()
        {
            std::cout << "SmallClass destory " << x << std::endl;
        }

        int GetX()
        {
            return x;
        }

    private:
        int x;
};

int main()
{
    std::cout << "main start" << std::endl;

    // scoped_ptr on basic
    boost::scoped_ptr<int> int_ptr(new int);
    *int_ptr = 100;
    ++*int_ptr;
    std::cout << *int_ptr << std::endl;

    // scoped_ptr on class
    boost::scoped_ptr<SmallClass> sc_ptr(new SmallClass(0));
    std::cout << sc_ptr->GetX() << std::endl;
    sc_ptr.reset();
    std::cout << "main end" << std::endl;

    return 0;    
}

最后再强调下scoped_ptr常用方法:

假设:

scoped_ptr<T> ptr_t(new T); // 假设内置指针为p_t
  • ptr_t->get(),返回内部管理的指针,但禁止在get()出来的指针上执行delete
  • ptr_t->XXX(),等同于p_t->XXX()
  • ptr_t.reset(),delete内部持有的p_t。
  • 假设T支持直接赋值,*ptr_t = xxx。
  • 再次强调,scoped_ptr不能做赋值、拷贝等转移指针管理权限的事情。因此,class内置域为scoped_ptr<T>是不允许的,除非class也禁止拷贝、复制!

scoped_array

同scoped_ptr基本一样,只不过可接受数组的new [],多了下标访问操作,其他类似。

例子如下:

#include <boost/smart_ptr.hpp>
#include <iostream>

int main()
{
    boost::scoped_array<int> ptr_arr(new int[10]);
    for(int i=0; i<10; i++)
    {
        ptr_arr[i] = i;
    }
    std::cout << ptr_arr[1] << std::endl;
    std::cout << ptr_arr.get()[1] << std::endl;
    return 0;
}

shared_ptr

目前已成为tr1标准的一部分,发展自原始的auto_ptr,内置引用计数,支持拷贝、支持 =赋值

使用时候有3点要特别注意:

  1. 禁止get()得到指针地址后,执行delete,这个同scoped_ptr。
  2. 禁止循环引用,否则会出内存泄漏。
  3. 不要将shared_ptr用于函数的临时参数:
// 下面这个是OK的。
void ok()
{
    shared_ptr<int> p(new int(2));
    f(p, g());
}

// 下面这个就可能内存泄漏!
void bad()
{
    f(shared_ptr<int>(new int(2)), g());
}

看看基本的使用例子。

#include <boost/smart_ptr.hpp>
#include <iostream>

int main()
{
    // Basic useage
    boost::shared_ptr<int> p1(new int(10));
    std::cout << "ref count of p1: " << p1.use_count() << std::endl;
    boost::shared_ptr<int> p2(p1); // or p2 = p1;
    std::cout << "ref count of p1: " << p1.use_count() << std::endl;
    *p1 = 999;
    std::cout << "*p2: " << *p2 << std::endl;
    p2.reset();
    std::cout << "ref count of p1: " << p1.use_count() << std::endl;
    return 0;
}

然后来讨论类中成员使用shared_ptr的例子,先扯一个稍微远一点的。假设我们类的成员需要new出来,先来看一个错误的:

#include <boost/shared_ptr.hpp>
#include <iostream>

class ClassV1
{
  public:

      ClassV1(int data_param)
      {
          data = NULL;
          Init(data_param);
          std::cout << "construct" << std::endl;
      }

      virtual ~ClassV1()
      {
          if(data)
          {
              delete data;
          }
          data = NULL;
      }

      void Init(int data_param)
      {
          if(data)
          {
              delete data;
          }
          data = new int(data_param);
      }

  private:
      int* data;
};

int main()
{
    ClassV1 c1(10);
    ClassV1 c2(c1);
    ClassV1 c3 = c2;
    return 0;
}

上面的ClassV1没有问题,但是会生成默认的拷贝、赋值构造函数,其只拷贝值!于是,当c3=c2或者c2(c1)时, 指针data的地址被复制了多份,c1、c2、c3各持有一份,析构的时候就被delete了3次,于是memory error是必须的了。

怎么办?第一个传统做法就是,c1、c2、c3各保存独立的data区域,即深拷贝:自己写拷贝构造、赋值构造函数。

#include <boost/shared_ptr.hpp>
#include <iostream>

class ClassV1
{
  public:

      ClassV1(int data_param)
      {
          data = NULL;
          Init(data_param);
          std::cout << "construct" << std::endl;
      }

      virtual ~ClassV1()
      {
          if(data)
          {
              delete data;
          }
          data = NULL;
      }

      ClassV1(const ClassV1& rhs)
      {
          std::cout << "copy" << std::endl;
          data = NULL;
          Init(*rhs.data);
      }

      ClassV1& operator = (const ClassV1& rhs)
      {
          int* p_old = data;
          data = new int(*p_old);
          delete p_old;
          return *this;
      }

      void Init(int data_param)
      {
          if(data)
          {
              delete data;
          }
          data = new int(data_param);
      }

  private:
      int* data;
};

int main()
{
    ClassV1 c1(10);
    ClassV1 c2(c1);
    ClassV1 c3 = c2;
    return 0;
}

现在我们假设另外一种情况,即data仍然需要从堆上new出来,但可以被若干实例共享。此时可以用shared_ptr,而且甚至不需要编写拷贝构造、=赋值构造,就可以。如下:

#include <boost/shared_ptr.hpp>
#include <iostream>

class ClassV1
{
  public:

      ClassV1(int data)
      :ptr_data(new int)
      {
          std::cout << "construct" << std::endl;
          Init(data);
      }

      void Init(int data)
      {
          *ptr_data = data;
      }

      int PtrUseCount()
      {
         return ptr_data.use_count(); 
      }

      int Data()
      {
          return *ptr_data;
      }

  private:
      boost::shared_ptr<int> ptr_data;
};

int main()
{
    ClassV1 c1(10);
    ClassV1 c2(c1);
    ClassV1 c3 = c2;
    c1 = c1;
    std::cout << c1.PtrUseCount() << std::endl;
    std::cout << c2.Data() << std::endl;
    return 0;
}

实际上,是c++编译器自动生成的拷贝、=赋值构造函数完成了ptr_data的赋值拷贝工作,而智能指针赋值拷贝的同时,引用计数也加1了。在默认析构函数也是如此,析构函数执行之后,会调用所有成员(ptr_data)的析构函数,检查引用计数都为0后,会delete掉这个int。

从而完美的完成了无内存泄漏的、无内存出错的、多个实例之间的指针变量共享。

最后,需要指出的是,由于shared_ptr实现了拷贝构造、=赋值构造等函数,因此可以完美的放入STL容器中,看下面这个例子。

#include <boost/smart_ptr.hpp>
#include <iostream>
#include <vector>

int main()
{
    // Make
    std::vector< boost::shared_ptr<int> > vec;
    for(int i=0; i<10; i++)
    {
        vec.push_back(boost::shared_ptr<int>(new int(i)));
    }
    // Print
    std::cout << *vec[1] << std::endl;
    return 0;
}

shared_array

同scoped_array类似,shared_array是shared_ptr的数组版本,不再赘述。

weak_ptr

shared_ptr看起来已经很完美了,但有个致命缺陷:不能管理循环引用的对象。

看如下的例子:

#include <string>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

class parent;
class children;

typedef boost::shared_ptr<parent> parent_ptr;
typedef boost::shared_ptr<children> children_ptr;

class parent
{
public:
    ~parent() { std::cout <<"destroying parent\n"; }

public:
    children_ptr children;
};

class children
{
public:
    ~children() { std::cout <<"destroying children\n"; }

public:
    parent_ptr parent;
};

void test()
{
    parent_ptr father(new parent());
    children_ptr son(new children);

    father->children = son;
    son->parent = father;
}

void main()
{
    std::cout<<"begin test...\n";
    test();
    std::cout<<"end test.\n";
}

由于parent和child相互引用,他们的计数永远都为1,所以这样使用shared_ptr必然会导致内存泄漏。

boost::weak_ptr必须从一个boost::share_ptr或另一个boost::weak_ptr转换而来,这也说明,进行该对象的内存管理的是那个强引用的boost::share_ptr。boost::weak_ptr只是提供了对管理对象的一个访问手段。

弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。

还有两个常用的功能函数:expired()用于检测所管理的对象是否已经释放;lock()用于获取所管理的对象的强引用指针。

class children
{
public:
    ~children() { std::cout <<"destroying children\n"; }

public:
    boost::weak_ptr<parent> parent;
};

再次强调,weak_ptr必须从shared_ptr而来。

#include <boost/smart_ptr.hpp>
#include <iostream>

int main()
{
    boost::shared_ptr<int> ptr(new int(10));
    std::cout << ptr.use_count() << std::endl;
    boost::weak_ptr<int> ptr_weak(ptr);
    std::cout << ptr.use_count() << std::endl;
    std::cout << ptr_weak.expired() << std::endl;
    return 0;
}

intrusive_ptr

如果说shared_ptr是自动挡,那么intrusive_ptr就是对应的手动挡。

intrusive_ptr是一种“侵入式”的引用计数指针,实际并不提供引用计数功能,而是要求被存储的对象自己实现引用计数功能。

它提供intrusive_ptr_add_ref和intrusive_ptr_release函数接口供boost::intrusive_ptr调用。

Leave a Reply

Your email address will not be published. Required fields are marked *