Boost智能指针:shared_ptr

共享指针  ( shared_ptr ) 是现在的 Boost 库中提供的,并且应该是将来  C++1x  的标准库中提供的一个模板类。在此之前,ISO/IEC 14882:2003 标准库 <memory> 中的“自动指针 (auto_ptr)”也有类似的功能。显然 shared_ptr 要比 auto_ptr 从功能上来说应该强大一些。这篇文章主要介绍 shared_ptr 的最基本用法和注意事项,相当于入门级多一点点。

1. shared_ptr 的功能

shared_ptr 主要的功能是,管理动态创建的对象的销毁。它的基本原理就是记录对象被引用的次数,当引用次数为 0 的时候,也就是最后一个指向某对象的共享指针析构的时候,共享指针的析构函数就把指向的内存区域释放掉。

共享指针对象重载了 operator* 和 operator-> , 所以你可以像通常的指针一样使用它。这部分不再赘述。

2. shared_ptr 所在库

2.1. 对于 Visual C++ 2010

目前,Visual C++ 2010 的 <memory> 库里,已经包含了 shared_ptr 模板类,也就是说,你可以直接这样写:

#include <memory>

2.2. 对于其它支持 ISO/IEC 14882:2003 标准的编译器

而 GNU G++ 的标准库中还没有支持(毕竟是将来的标准),如果在 G++ 中想使用 shared_ptr, 还是得用到 Boost 库,就是说,在 G++ 里,你得这样写:

#include <boost/shared_ptr.hpp>

3. shared_ptr 的构造

保险起见,你应该仅从以下几种途径构造一个共享指针(以下例子中若没特殊说明,T 就代表共享指针所指向的对象的类型):

3.0. 使用空参数构造函数构造

也就是说,你可以直接定义一个 shared_ptr 而不指定构造函数的内容:

1
shared_ptr <T > ptr ;

这样做的话,ptr 的意义就相当于一个 NULL 指针。当你试图在一个空指针上做类似于 *ptr 或者 ptr->xx 之类的东西的时候,应该会收到异常的。

3.1. 直接从 new 操作符的返回值构造

用代码来表示,就是可以这样使用:

1
shared_ptr <T > ptr ( new T ( ) ) ;

3.2. 使用复制构造函数(或等号重载),从其它 shared_ptr 的对象构造

一种显然的情况是这样的:

1
2
shared_ptr <T > ptr1 ( new T ( ) ) ;  // 本行与 3.1. 中的构造方法是一样的
shared_ptr <T > ptr2 (ptr1 ) ;     // 这就是使用复制构造函数的方法,会让引用计数加 1

还有,shared_ptr 可以当作函数的参数传递,或者当作函数的返回值返回,这个时候其实也相当于使用复制构造函数。

3.3. 从 shared_ptr 提供的类型转换 (cast) 函数的返回值构造

shared_ptr 也可以类型转换,有关类型转换的详情参见下面的 5. 此处假设 B 是 A 的子类,那么,在 C 语言中 B 的指针当然是可以转换成 A 的指针的。在共享指针里,应该这样做:

1
2
shared_ptr <B > ptrb ( new B ( ) ) ;
shared_ptr <A > ptra ( dynamic_pointer_cast <A > (ptrb )  ) ;

4. shared_ptr 的“赋值”

shared_ptr 也可以直接赋值,但是必须是赋给相同类型的 shared_ptr 对象,而不能是普通的 C 指针或 new 运算符的返回值。当共享指针 a 被赋值成 b 的时候,如果 a 原来是 NULL, 那么直接让 a 等于 b 并且让它们指向的东西的引用计数加 1; 如果 a 原来也指向某些东西的时候,如果 a 被赋值成 b, 那么原来 a 指向的东西的引用计数被减 1, 而新指向的对象的引用计数加 1. 就是说以下代码是允许的:

1
2
3
shared_ptr <T > a ( new T ( ) ) ;
shared_ptr <T > b ( new T ( ) ) ;
= b ;  // 此后 a 原先所指的对象会被销毁,b 所指的对象引用计数加 1

shared_ptr 的对象在构造之后,可以被赋予空值,此时使用的应该是 reset() 函数,如:

1
2
shared_ptr <T > a ( new T ( ) ) ;
a. reset ( ) ;  // 此后 a 原先所指的对象会被销毁,并且 a 会变成 NULL

当然理论上也可以这样写:

1
2
shared_ptr <T > a ( new T ( ) ) ;
= shared_ptr <T > ( ) ;  // 相当于给 a 赋一个新构造的 shared_ptr, 也就是 NULL

5. shared_ptr 的类型转换

shared_ptr 有两种类型转换的函数,一个是 static_pointer_cast, 一个是 dynamic_pointer_cast. 其实用法真的和 C++ 提供的 static_cast 和 dynamic_cast 很像,再结合 3.3. 的代码和以下类似的代码,几乎没什么好讲的:

1
2
3
shared_ptr <A > ptra ;
shared_ptr <B > ptrb ( new B ( ) ) ;
ptra  = dynamic_pointer_cast <A > (ptrb ) ;

6. 从 shared_ptr 的对象获得传统 C 指针

很简单,可以这样用:

1
2
shared_ptr <T > ptr ( new T ( ) ) ;
*= ptr. get ( ) ;  // 获得传统 C 指针

7. shared_ptr 的常见的其它用法

比如,“我想让一个已经构造好的共享指针,丢弃掉原来所指的对象(或者让其引用计数减 1),然后指向一个新的 new 出来的对象,该怎么办?”参考如下代码:

1
2
shared_ptr <T > ptr ( new T ( ) ) ;
ptr. reset ( new T ( ) ) ;  // 原来所指的对象会被销毁

8. shared_ptr 的错误用法

一定要注意,本节所述所有方法,都是错误的!

8.1. 在“中途”使用传统 C 指针构造共享指针

所谓在中途,指的就是不从 new 的返回值直接构造共享指针,比如从 this 指针构造自己的共享指针等。

8.2. 从一个对象的传统 C 指针,构造出两个或以上的共享指针

其实这种和 8.1. 也是类似的,或者说,这种情况是 8.1. 的一种具体情况,比如,下面的代码是错误的:

1
2
3
*=  new T ( ) ;
shared_ptr <T > ptr1 (a ) ;
shared_ptr <T > ptr2 (a ) ;

这样的话,ptr1 和 ptr2 的引用计数是单独算的,它们任意一个对象在析构的时候,都会销毁 a 所指的对象,所以,这个对象会被“销毁两次”。

9. shared_ptr 的局限

有关运行效率的问题在这里就不讨论了。其它方面,shared_ptr 的构造要求比较高,如果对象在创建的时候没有使用共享指针存储的话,之后也不能用共享指针管理这个对象了。如果有引用循环 (reference cycle), 也就是对象 a 有指向对象 b 的共享指针,对象 b 也有指向对象 a 的共享指针,那么它们都不会被析构。当然的,shared_ptr 也没有办法和 Garbage Collecting 比较,毕竟如果运行库能够干预,还是有算法可以检查到引用循环的。(例如求强连通分量的算法。)

尤其,在类的成员函数的编写的时候,有时我们经常希望得到“自己”的共享指针,但是这往往是无法得到的。此时也不能够从 this 指针构造自己的共享指针(参见 8.1.),所以有时很憋闷。

10. 总结

实际上上面这么多注意事项,中心思想就是一个:让 shared_ptr 正确地记录对象被引用次数。如果能悟出一点 shared_ptr 的工作原理,基本上不会弄出太危险的事情来。

转载于:http://euyuil.com/2344/

 

boost::shared_ptr的特点:

和前面介绍的boost::scoped_ptr相比,boost::shared_ptr可以共享对象的所有权,因此其使用范围基本上没有什么限制(还是有一些需要遵循的使用规则,下文中介绍),自然也可以使用在stl的容器中。另外它还是线程安全的,这点在多线程程序中也非常重要。

boost::shared_ptr的使用规则:

boost::shared_ptr并不是绝对安全,下面几条规则能使我们更加安全的使用boost::shared_ptr:

  1. 避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放
  2. shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)。
  3. 不要构造一个临时的shared_ptr作为函数的参数。
    如下列代码则可能导致内存泄漏:
    void test()
    {
        foo(boost::shared_ptr<implementation>(new    implementation()),g());
    }
    正确的用法

    void test()
    {
        boost::shared_ptr<implementation> sp    (new implementation());
        foo(sp,g());
    }
     //当函数g()抛异常的时候就会泄露了,这个是boost文档上特地注明的标准bad Practices,Effective C++ Third Edition 条款17 有讲到

你可能感兴趣的:(Boost智能指针:shared_ptr)