首页 > 代码库 > Boost智能指针-基础篇

Boost智能指针-基础篇

简介

内存管理一直是 C++ 一个比较繁琐的问题,而智能指针却可以很好的解决这个问题,在初始化时就已经预定了删除,排解了后顾之忧。1998年修订的第一版C++标准只提供了一种智能指针:std::auto_ptr(现以废弃),它基本上就像是个普通的指针:通过地址来访问一个动态分配的对象。std::auto_ptr之所以被看作是智能指针,是因为它会在析构的时候调用delete操作符来自动释放所包含的对象。当然这要求在初始化的时候,传给它一个由new操作符返回的对象的地址。既然std::auto_ptr的析构函数会调用delete操作符,它所包含的对象的内存会确保释放掉。这是智能指针的一个优点。
当尝试和异常联系起来时这就更加重要了:没有std::auto_ptr这样的智能指针,每一个动态分配内存的函数都需要捕捉所有可能的异常,以确保在异常传递给函数的调用者之前将内存释放掉。Boost C++ 库的智能指针系列提供了许多可以用在各种场合的智能指针。


RAII

RAII全称是“Resource acquisition is initialization”,直译为“资源获取就是初始化”。但是这翻译并没有显示出这个惯用法的真正内涵。RAII的好处在于它提供了一种资源自动管理的方式,当产生异常、回滚等现象时,RAII可以正确地释放掉资源。 
RAII也是智能指针的基本原理,智能指针只是这个习语的其中一例。智能指针确保在任何情况下,动态分配的内存都能得到正确释放,从而将开发人员从这项任务中解放了出来。 这包括程序因为异常而中断,原本用于释放内存的代码被跳过的场景。用一个动态分配的对象的地址来初始化智能指针,在析构的时候释放内存,就确保了这一点。因为析构函数总是会被执行的,这样所包含的内存也将总是会被释放。
许多的 C++ 应用程序都需要动态管理内存,因而智能指针是一种很重要的 RAII 类型,不过 RAII 本身是适用于许多其它场景的。


scoped_ptr

boost::scoped_ptr是一个简单的智能指针,它能够保证在离开作用域后对象被自动释放。
boost::scoped_ptr的实现是利用了一个栈上的对象去管理一个堆上的对象,从而使得堆上的对象随着栈上的对象销毁时自动删除。
boost::scoped_ptr 特点

  • 不能转换所有权
    scoped_ptr所管理的对象生命周期仅仅局限于一个区间(该指针所在的"{}"之间),无法传到区间之外,这就意味着scoped_ptr对象是不能作为函数的返回值的。
  • 不能共享所有权
    这个特点一方面使得该指针简单易用。另一方面也造成了功能的薄弱:不能用于STL的容器中。
  • 不能用于管理数组对象
    由于scoped_ptr是通过delete来删除所管理对象的,而数组对象必须通过deletep[]来删除,因此scoped_ptr是不能管理数组对象的,如果要管理数组对象需要使用boost::scoped_array类。

shared_ptr

boost::scoped_ptr虽然简单易用,但它不能共享所有权的特性却大大限制了其使用范围,shared_ptr可以解决这一局限。在标准C++中为 std::shared_ptr ,在Boost C++库里,这个智能指针命名为boost::shared_ptr。
shared_ptr的管理机制其实并不复杂,就是对所管理的对象进行了引用计数,当新增一个shared_ptr对该对象进行管理时,就将该对象的引用计数加一;减少一个shared_ptr对该对象进行管理时,就将该对象的引用计数减一,如果该对象的引用计数为0的时候,说明没有任何指针对其管理,才调用delete释放其所占的内存。

boost::shared_ptr 特点

  • 可以共享对象的所有权
    和scoped_ptr相比shared_ptr可以共享对象的所有权,所以保存在容器中的拷贝(包括容器在需要时额外创建的拷贝)都是和原件相同的,可以在标准容器中安全的使用动态分配的对象。
  • 线程安全的
    shared_ptr 对象提供与内建类型一样的线程安全级别。一个 shared_ptr 实例可以同时被多个线程“读”(仅使用不变操作进行访问)。 不同的 shared_ptr 实例可以同时被多个线程“写入”(使用类似 operator= 或 reset 这样的可变操作进行访问)(即使这些实例是拷贝,而且共享下层的引用计数)。 任何其它的同时访问的结果会导致未定义行为。”
    • 同一个shared_ptr被多个线程“读”是安全的。
    • 同一个shared_ptr被多个线程“写”是不安全的。
    • 共享引用计数的不同的shared_ptr被多个线程”写“是安全的。
  • 支持自定义的deleter

boost::shared_ptr 注意事项

  • shared_ptr可以当做函数的参数,也可以当做函数的返回值,这时候相当于使用复制构造
  • 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";
}

运行该程序可以看到,即使退出了test函数后,由于parent和children对象互相引用,它们的引用计数都是1,不能自动释放,并且此时这两个对象再无法访问到,这就引起了内存泄漏。
一般来讲,解除这种循环引用有下面有三种可行的方法:

  • 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
  • 当parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent。
  • 使用弱引用的智能指针打破这种循环引用。在父对子引用时使用强引用,子对父引用时使用弱引用,从而避免了循环引用。

虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。这里主要介绍一下第三种方法和boost中的弱引用的智能指针boost::weak_ptr。

强引用
一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。

弱引用
它仅仅是对象存在时候的引用,当对象不存在时弱引用能够检测到,从而避免非法访问,弱引用也不会修改对象的引用计数。这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。

在C++中,普通指针可看做弱引用,智能指针可看做强引用,尽管指针不能算"真正"的弱引用,因为弱引用应该能知道何时对象变成不可访问的了。

打破循环引用
在父对子引用时使用强引用,子对父引用时使用弱引用,从而避免了循环引用。

//详细例子见 Boost智能指针-weak_ptr
class children
{
public:
    ~children() { std::cout <<"destroying children\n"; }

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

虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。


intrusive_ptr

在多数情况下,我们不使用 boost::intrusive_ptr, 因为共享所有权的功能已在 boost::shared_ptr中提供,而且非插入式智能指针比插入式智能指针更灵活。
但是,有时候也会需要插入式的引用计数,可能是由于旧的代码,或者是为了与第三方的类进行集成。当有这种需要时,可以用 intrusive_ptr ,它具有与其它Boost智能指针相同的语义。如果你使用过其它的Boost智能指针,你就会发现不论是否插入式的,所有智能指针都有一致的接口。
使用intrusive_ptr的类必须可以提供引用计数。intrusive_ptr 通过调用两个函数,intrusive_ptr_add_ref 和 intrusive_ptr_release来管理引用计数;这两个函数必须正确地操作插入式的引用计数,以保证 intrusive_ptr正确工作。在使用intrusive_ptr的类中已经内置有引用计数的情况下,实现对intrusive_ptr的支持就是实现这两个函数。有些情况下,可以创建这两个函数的参数化版本,然后对所有带插入式引用计数的类型使用相同的实现。多数时候,声明这两个函数的最好的地方就是它们所支持的类型所在的名字空间。 在以下情况时使用 intrusive_ptr

  • 需要把 this 当作智能指针来使用。
  • 已有代码使用或提供了插入式的引用计数。
  • 智能指针的大小必须与裸指针的大小相等。

对比boost::shared_ptr

使用boost::shared_ptr用户类本省不需要具有引用计数功能,而是由boost::shared_ptr来提供;使用boost::shared_ptr的一大陷阱就是用一个raw pointer多次创建boost::shared_ptr,这将导致该raw pointer被多次销毁当boost::shared_ptr析构时。即不能如下使用:

int *a = new int(5);
boost::shared_ptr ptr1(a);
boost::shared_ptr ptr2(a);  //错误! 

boost::intrusive_ptr完全具备boost::shared_ptr的功能,且不存在shared_ptr的问题,即可以利用raw pointer创建多个intrusive _ptr,其原因就在于引用计数的ref_count对象,shared_ptr是放在shared_ptr结构里,而目标对象T通过继承intrusive_ptr_base将引用计数作为T对象的内部成员变量,就不会出现同一个对象有两个引用计数器的情况出现。

那么为什么通常鼓励大家使用shared_ptr,而不是intrusive_ptr呢, 在于shared_ptr不是侵入性的,可以指向任意类型的对象; 而intrusive_ptr所要指向的对象,需要继承intrusive_ptr_base,即使不需要,引用计数成员也会被创建。


参考链接
http://zh.wikipedia.org/wiki/%E5%BC%B1%E5%BC%95%E7%94%A8 http://www.codeproject.com/Articles/8394/Smart-Pointers-to-boost-your-code http://www.cnblogs.com/TianFang/archive/2008/09/15/1291050.html http://www.cnblogs.com/TianFang/archive/2008/09/19/1294521.html http://www.cnblogs.com/TianFang/archive/2008/09/20/1294590.html http://blog.csdn.net/alai04/article/details/572959 http://blog.csdn.net/yockie/article/details/8840205 http://blog.csdn.net/juana1/article/details/6624222 http://blog.csdn.net/ithzhang/article/details/9038929 http://blog.csdn.net/hunter8777/article/details/6327704 http://blog.csdn.net/yusiguyuan/article/details/22037833


From:http://blog.csdn.net/liufei_learning/article/details/34808549