Boost智能指针——shared_ptr

boost::scoped_ptr虽然简单易用,但它不能共享所有权的特性却大大限制了其使用范围,而boost::shared_ptr可以解决这一局限。顾名思义,boost::shared_ptr是可以共享所有权的智能指针,首先让我们通过一个例子看看它的基本用法:

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

class implementation
{
public:
    ~implementation() { std::cout <<"destroying implementation\n"; }
    void do_something() { std::cout << "did something\n"; }
};

void test()
{
    boost::shared_ptr<implementation> sp1(new implementation());
    std::cout<<"The Sample now has "<<sp1.use_count()<<" references\n";

    boost::shared_ptr<implementation> sp2 = sp1;
    std::cout<<"The Sample now has "<<sp2.use_count()<<" references\n";
    
    sp1.reset();
    std::cout<<"After Reset sp1. The Sample now has "<<sp2.use_count()<<" references\n";

    sp2.reset();
    std::cout<<"After Reset sp2.\n";
}

void main()
{
    test();
}

该程序的输出结果如下:

The Sample now has 1 references
The Sample now has 2 references
After Reset sp1. The Sample now has 1 references
destroying implementation
After Reset sp2.

可以看到,boost::shared_ptr指针sp1和sp2同时拥有了implementation对象的访问权限,且当sp1和sp2都释放对该对象的所有权时,其所管理的的对象的内存才被自动释放。在共享对象的访问权限同时,也实现了其内存的自动管理。

boost::shared_ptr的内存管理机制:

boost::shared_ptr的管理机制其实并不复杂,就是对所管理的对象进行了引用计数,当新增一个 boost::shared_ptr对该对象进行管理时,就将该对象的引用计数加一;减少一个boost::shared_ptr对该对象进行管理时,就将该对象的引用计数减一,如果该对象的引用计数为0的时候,说明没有任何指针对其管理,才调用delete释放其所占的内存。

上面的那个例子可以的图示如下:

  1. sp1对implementation对象进行管理,其引用计数为1
  2. 增加sp2对implementation对象进行管理,其引用计数增加为2
  3. sp1释放对implementation对象进行管理,其引用计数变为1
  4. sp2释放对implementation对象进行管理,其引用计数变为0,该对象被自动删除

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());
      }

 

shared_ptr

头文件: "boost/shared_ptr.hpp"

shared_ptr 可以从一个裸指针、另一个shared_ptr、一个std::auto_ptr、或者一个boost::weak_ptr构造。还可以传递第二个参数给shared_ptr的构造函数,它被称为删除器(deleter)。删除器稍后会被调用,来处理共享资源的释放。这对于管理那些不是用new分配也不是用delete释放的资源时非常有用(稍后将看到创建客户化删除器的例子)。shared_ptr被创建后,它就可象普通指针一样使用了,除了一点,它不能被显式地删除。

以下是shared_ptr的部分摘要;最重要的成员和相关普通函数被列出,随后是简单的讨论。

成员函数

template <class Y> explicit shared_ptr(Y* p);

这个构造函数获得给定指针p的所有权。参数 p 必须是指向 Y 的有效指针。构造后引用计数设为1。唯一从这个构造函数抛出的异常是std::bad_alloc (仅在一种很罕见的情况下发生,即不能获得引用计数器所需的自由空间)。

template <class Y,class D> shared_ptr(Y* p,D d);

这个构造函数带有两个参数。第一个是shared_ptr将要获得所有权的那个资源,第二个是shared_ptr被销毁时负责释放资源的一个对象,被保存的资源将以d(p)的形式传给那个对象。因此p的值是否有效取决于d。如果引用计数器不能分配成功,shared_ptr抛出一个类型为std::bad_alloc的异常。

shared_ptr(const shared_ptr& r);

r中保存的资源被新构造的shared_ptr所共享,引用计数加一。这个构造函数不会抛出异常。

template <class Y> explicit shared_ptr(const weak_ptr<Y>& r);

从一个weak_ptr (本章稍后会介绍)构造shared_ptr。这使得weak_ptr的使用具有线程安全性,因为指向weak_ptr参数的共享资源的引用计数将会自增(weak_ptr不影响共享资源的引用计数)。如果weak_ptr为空 (r.use_count()==0), shared_ptr 抛出一个类型为bad_weak_ptr的异常。

template <typename Y> shared_ptr(std::auto_ptr<Y>& r);

这个构造函数从一个auto_ptr获取r中保存的指针的所有权,方法是保存指针的一份拷贝并对auto_ptr调用release。构造后的引用计数为1。而r当然就变为空的。如果引用计数器不能分配成功,则抛出 std::bad_alloc

~shared_ptr();

shared_ptr析构函数对引用计数减一。如果计数为零,则保存的指针被删除。删除指针的方法是调用operator delete 或者,如果给定了一个执行删除操作的客户化删除器对象,就把保存的指针作为唯一参数调用这个对象。析构函数不会抛出异常。

shared_ptr& operator=(const shared_ptr& r);  

赋值操作共享r中的资源,并停止对原有资源的共享。赋值操作不会抛出异常。

void reset();

reset函数用于停止对保存指针的所有权的共享。共享资源的引用计数减一。

T& operator*() const;

这个操作符返回对已存指针所指向的对象的一个引用。如果指针为空,调用operator* 会导致未定义行为。这个操作符不会抛出异常。

T* operator->() const;

这个操作符返回保存的指针。这个操作符与operator*一起使得智能指针看起来象普通指针。这个操作符不会抛出异常。

T* get() const;

get函数是当保存的指针有可能为空时(这时 operator*operator-> 都会导致未定义行为)获取它的最好办法。注意,你也可以使用隐式布尔类型转换来测试 shared_ptr 是否包含有效指针。这个函数不会抛出异常。

bool unique() const;

这个函数在shared_ptr是它所保存指针的唯一拥有者时返回 true ;否则返回 falseunique 不会抛出异常。

long use_count() const;
      

use_count 函数返回指针的引用计数。它在调试的时候特别有用,因为它可以在程序执行的关键点获得引用计数的快照。小心地使用它,因为在某些可能的shared_ptr实现中,计算引用计数可能是昂贵的,甚至是不行的。这个函数不会抛出异常。

operator unspecified-bool-type() const;

这是个到unspecified-bool-type类型的隐式转换函数,它可以在Boolean上下文中测试一个智能指针。如果shared_ptr保存着一个有效的指针,返回值为True;否则为false。注意,转换函数返回的类型是不确定的。把返回类型当成bool用会导致一些荒谬的操作,所以典型的实现采用了safe bool idiom,[8] 它很好地确保了只有可适用的Boolean测试可以使用。这个函数不会抛出异常。

[8] 由Peter Dimov发明的。

void swap(shared_ptr<T>& b);

这可以很方便地交换两个shared_ptrswap 函数交换保存的指针(以及它们的引用计数)。这个函数不会抛出异常。

普通函数

template <typename T,typename U>
shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r);

要对保存在shared_ptr里的指针执行static_cast,我们可以取出指针然后强制转换它,但我们不能把它存到另一个shared_ptr里;新的 shared_ptr 会认为它是第一个管理这些资源的。解决的方法是用 static_pointer_cast. 使用这个函数可以确保被指物的引用计数保持正确。static_pointer_cast 不会抛出异常。

用法

使用shared_ptr解决的主要问题是知道删除一个被多个客户共享的资源的正确时机。下面是一个简单易懂的例子,有两个类 AB, 它们共享一个int实例。使用 boost::shared_ptr, 你需要必须包含 "boost/shared_ptr.hpp".

#include "boost/shared_ptr.hpp"
#include <cassert>

class A {
boost::shared_ptr<int> no_;
public:
A(boost::shared_ptr<int> no) : no_(no) {}
void value(int i) {
*no_=i;
}
};

class B {
boost::shared_ptr<int> no_;
public:
B(boost::shared_ptr<int> no) : no_(no) {}
int value() const {
return *no_;
}
};

int main() {
boost::shared_ptr<int> temp(new int(14));
A a(temp);
B b(temp);
a.value(28);
assert(b.value()==28);
}

AB都保存了一个 shared_ptr<int>. 在创建 AB的实例时,shared_ptr temp 被传送到它们的构造函数。这意味着共有三个 shared_ptra, b, 和 temp,它们都引向同一个int实例。如果我们用指针来实现对一个的共享,AB 必须能够在某个时间指出这个int要被删除。在这个例子中,直到main的结束,引用计数为3,当所有 shared_ptr离开了作用域,计数将达到0,而最后一个智能指针将负责删除共享的 int.

回顾Pimpl用法

前一节展示了使用scoped_ptr的pimpl 用法,如果使用这种用法的类是不允许复制的,那么scoped_ptr在保存pimpl的动态分配实例时它工作得很好。但是这并不适合于所有想从pimpl用法中获益的类型(注意,你还可以用 scoped_ptr,但必须手工实现复制构造函数和赋值操作符)。对于那些可以处理共享的实现细节的类,应该用 shared_ptr。当pimpl的所有权被传递给一个 shared_ptr, 复制和赋值操作都是免费的。你可以回忆起,当使用 scoped_ptr 去处理pimpl类的生存期时,对封装类的复制是不允许的,因为 scoped_ptr是不可复制的。这意味着要使这些类支持复制和赋值,你必须手工定义复制构造函数和赋值操作符。当使用 shared_ptr 去处理pimpl类的生存期时,就不再需要用户自己定义复制构造函数了。注意,这时pimpl实例是被该类的多个对象所共享,因此如果规则是每个pimpl实例只能被类的一个实例使用,你还是要手工编写复制构造函数。解决的方法和我们在scoped_ptr那看到的很相似,只是把scoped_ptr换成了shared_ptr

shared_ptr 与标准库容器

把对象直接存入容器中有时会有些麻烦。以值的方式保存对象意味着使用者将获得容器中的元素的拷贝,对于那些复制是一种昂贵的操作的类型来说可能会有性能的问题。此外,有些容器,特别是 std::vector, 当你加入元素时可能会复制所有元素,这更加重了性能的问题。最后,传值的语义意味着没有多态的行为。如果你需要在容器中存放多态的对象而且你不想切割它们,你必须用指针。如果你用裸指针,维护元素的完整性会非常复杂。从容器中删除元素时,你必须知道容器的使用者是否还在引用那些要删除的元素,不用担心多个使用者使用同一个元素。这些问题都可以用shared_ptr来解决。

下面是如何把共享指针存入标准库容器的例子。

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

class A {
public:
virtual void sing()=0;
protected:
virtual ~A() {};
};

class B : public A {
public:
virtual void sing() {
std::cout << "Do re mi fa so la";
}
};

boost::shared_ptr<A> createA() {
boost::shared_ptr<A> p(new B());
return p;
}

int main() {
typedef std::vector<boost::shared_ptr<A> > container_type;
typedef container_type::iterator iterator;

container_type container;
for (int i=0;i<10;++i) {
container.push_back(createA());
}

std::cout << "The choir is gathered: /n";
iterator end=container.end();
for (iterator it=container.begin();it!=end;++it) {
(*it)->sing();
}
}

这里有两个类, AB, 各有一个虚拟成员函数 sing. BA公有继承而来,并且如你所见,工厂函数 createA 返回一个动态分配的B的实例,包装在shared_ptr<A>里。在 main里, 一个包含shared_ptr<A>std::vector 被放入10个元素,最后对每个元素调用sing。如果我们用裸指针作为元素,那些对象需要被手工删除。而在这个例子里,删除是自动的,因为在vector的生存期中,每个shared_ptr的引用计数都保持为1;当 vector 被销毁,所有引用计数器都将变为零,所有对象都被删除。有趣的是,即使 A 的析构函数没有声明为 virtual, shared_ptr 也会正确调用 B的析构函数!

上面的例子示范了一个强有力的技术,它涉及A里面的protected析构函数。因为函数 createA 返回的是 shared_ptr<A>, 因此不可能对shared_ptr::get返回的指针调用 delete 。这意味着如果为了向某个需要裸指针的函数传送裸指针而从shared_ptr中取出裸指针的话,它不会由于意外地被删除而导致灾难。那么,又是如何允许 shared_ptr 删除它的对象的呢? 这是因为指针指向的真正类型是 B; 而B的析构函数不是protected的。这是非常有用的方法,用于给shared_ptr中的对象增加额外的安全性。

shared_ptr 与其它资源

有时你会发现你要把shared_ptr用于某个特别的类型,它需要其它清除操作而不是简单的 deleteshared_ptr可以通过客户化删除器来支持这种需要。那些处理象 FILE*这样的操作系统句柄的资源通常要使用象fclose这样的操作来释放。要在shared_ptr里使用 FILE* ,我们要定义一个类来负责释放相应的资源。

class FileCloser {
public:
void operator()(FILE* file) {
std::cout << "The FileCloser has been called with a FILE*, "
"which will now be closed./n";
if (file!=0)
fclose(file);
}
};

这是一个函数对象,我们用它来确保在资源要释放时调用 fclose 。下面是使用FileCloser类的示例程序。

int main() {
std::cout <<
"shared_ptr example with a custom deallocator./n";
{
FILE* f=fopen("test.txt","r");
if (f==0) {
std::cout << "Unable to open file/n";
throw "Unable to open file";
}

boost::shared_ptr<FILE>
my_shared_file(f, FileCloser());

// 定位文件指针
fseek(my_shared_file.get(),42,SEEK_SET);
}
std::cout << "By now, the FILE has been closed!/n";
}

注意,在访问资源时,我们需要对shared_ptr使用 &* 用法, get, 或 get_pointer。(请注意最好使用 &*. 另两个选择不太清晰) 这个例子还可以更简单,如果我们在释放资源时只需要调用一个单参数函数的话,就根本不需要创建一个客户化删除器类型。上面的例子可以重写如下:

{
FILE* f=fopen("test.txt","r");
if (f==0) {
std::cout << "Unable to open file/n";
throw file_exception();
}

boost::shared_ptr<FILE> my_shared_file(f,&fclose);

// 定位文件指针
fseek(&*my_shared_file,42,SEEK_SET);
}
std::cout << "By now, the FILE* has been closed!/n";

定制删除器在处理需要特殊释放程序的资源时非常有用。由于删除器不是 shared_ptr 类型的一部分,所以使用者不需要知道关于智能指针所拥有的资源的任何信息(当然除了如何使用它!)。例如,你可以使用对象池,定制删除器只需简单地把对象返还到池中。或者,一个 singleton 对象应该使用一个什么都不做的删除器。

使用定制删除器的安全性

我们已经看到对基类使用 protected 析构函数有助于增加使用shared_ptr的类的安全性。另一个达到同样安全级别的方法是,声明析构函数为 protected (或 private) 并使用一个定制删除器来负责销毁对象。这个定制删除器必须是它要删除的类的友元,这样它才可以工作。封装这个删除器的好方法是把它实现为私有的嵌套类,如下例所示:

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

class A {
class deleter {
public:
void operator()(A* p) {
delete p;
}
};
friend class deleter;
public:

virtual void sing() {
std::cout << "Lalalalalalalalalalala";
}

static boost::shared_ptr<A> createA() {
boost::shared_ptr<A> p(new A(),A::deleter());
return p;
}

protected:
virtual ~A() {};
};

int main() {
boost::shared_ptr<A> p=A::createA();
}

注意,我们在这里不能使用普通函数来作为 shared_ptr<A> 的工厂函数,因为嵌套的删除器是A私有的。使用这个方法,用户不可能在栈上创建 A的对象,也不可能对A的指针调用 delete

从this创建shared_ptr  

有时候,需要从this获得 shared_ptr ,即是说,你希望你的类被shared_ptr所管理,你需要把"自身"转换为shared_ptr的方法。看起来不可能?好的,解决方案来自于我们即将讨论的另一个智能指针boost::weak_ptrweak_ptrshared_ptr的一个观察者;它只是安静地坐着并看着它们,但不会影响引用计数。通过存储一个指向thisweak_ptr 作为类的成员,就可以在需要的时候获得一个指向thisshared_ptr。为了你可以不必编写代码来保存一个指向thisweak_ptr,接着又从weak_ptrshared_ptr得,Boost.Smart_ptr 为这个任务提供了一个助手类,称为 enable_shared_from_this. 只要简单地让你的类公有地派生自 enable_shared_from_this,然后在需要访问管理thisshared_ptr时,使用函数 shared_from_this 就行了。下面的例子示范了如何使用 enable_shared_from_this

#include "boost/shared_ptr.hpp"
#include "boost/enable_shared_from_this.hpp"

class A;

void do_stuff(boost::shared_ptr<A> p) {
...
}

class A : public boost::enable_shared_from_this<A> {
public:
void call_do_stuff() {
do_stuff(shared_from_this());
}
};

int main() {
boost::shared_ptr<A> p(new A());
p->call_do_stuff();
}

这个例子还示范了你要用shared_ptr管理this的情形。类 A 有一个成员函数 call_do_stuff 需要调用一个普通函数 do_stuff, 这个普通函数需要一个类型为 boost:: shared_ptr<A>的参数。现在,在 A::call_do_stuff里, this 不过是一个 A指针, 但由于 A 派生自 enable_shared_from_this, 调用 shared_from_this 将返回我们所要的 shared_ptr 。在enable_shared_from_this的成员函数 shared_from_this里,内部存储的 weak_ptr 被转换为 shared_ptr, 从而增加了相应的引用计数,以确保相应的对象不会被删除。

总结

引用计数智能指针是非常重要的工具。Boost的 shared_ptr 提供了坚固而灵活的解决方案,它已被广泛用于多种环境下。需要在使用者之间共享对象是常见的,而且通常没有办法通知使用者何时删除对象是安全的。shared_ptr 让使用者无需知道也在使用共享对象的其它对象,并让它们无需担心在没有对象引用时的资源释放。这对于Boost的智能指针类而言是最重要的。你会看到 Boost.Smart_ptr中还有其它的智能指针,但这一个肯定是你最想要的。通过使用定制删除器,几乎所有资源类型都可以存入 shared_ptr。这使得shared_ptr 成为处理资源管理的通用类,而不仅仅是处理动态分配对象。与裸指针相比,shared_ptr会有一点点额外的空间代价。我还没有发现由于这些代价太大而需要另外寻找一个解决方案的情形。不要去创建你自己的引用计数智能指针类。没有比使用 shared_ptr智能指针更好的了。

在以下情况时使用 shared_ptr

  • 当有多个使用者使用同一个对象,而没有一个明显的拥有者时

  • 当要把指针存入标准库容器时

  • 当要传送对象到库或从库获取对象,而没有明确的所有权时

  • 当管理一些需要特殊清除方式的资源时[9]

    [9] 通过定制删除器的帮助。

 

虽然boost.shared_ptr是个非常好的东西,使用它可以使得c++程序不需要考虑内存释放的问题,但是还是有很多必须注意的地方。下面罗列了一些本人在实际工作中经常碰到的使用shared_ptr出问题的几种情况。

1. shared_ptr多次引用同一数据,如下:
{
int* pInt = new int[100];
boost::shared_ptr<int> sp1(pInt);
// 一些其它代码之后…
boost::shared_ptr<int> sp2(pInt);
}

这种情况在实际中是很容易发生的,结果也是非常致命的,它会导致两次释放同一块内存,而破坏堆。

2.使用shared_ptr包装this指针带来的问题,如下:

class tester
{
public:
  tester()
  ~tester()
  {
    std::cout << "析构函数被调用!\n";
  }
public:
  boost::shared_ptr<tester> sget()
  {
    return boost::shared_ptr<tester>(this);
  }
};

int main()
{
  tester t;
  boost::shared_ptr<tester> sp =  t.sget(); // …
  return 0;
}

也将导致两次释放t对象破坏堆栈,一次是出栈时析构,一次就是shared_ptr析构。若有这种需要,可以使用下面代码。

class tester : public boost::enable_shared_from_this<tester>
{
public:
  tester()
  ~tester()
  {
  std::cout << "析构函数被调用!\n";
  }
public:
  boost::shared_ptr<tester> sget()
  {
  return shared_from_this();
  }
};

int main()
{
  boost::shared_ptr<tester> sp(new tester);
  // 正确使用sp 指针。
  sp->sget();
  return 0;
}

3. shared_ptr循环引用导致内存泄露,代码如下:

class parent;
class child;

typedef boost::shared_ptr<parent> parent_ptr;
typedef boost::shared_ptr<child> child_ptr;

class parent
{
public:
       ~parent() {
              std::cout <<"父类析构函数被调用.\n";
       }
public:
       child_ptr children;
};

class child
{
public:
       ~child() {
              std::cout <<"子类析构函数被调用.\n";
       }
public:
       parent_ptr parent;
};

int main()
{
  parent_ptr father(new parent());
  child_ptr son(new child);
  // 父子互相引用。
  father->children = son;
  son->parent = father;
  return 0;
}

如上代码,将在程序退出前,father的引用计数为2,son的计数也为2,退出时,shared_ptr所作操作就是简单的将计数减1,如果为0则释放,显然,这个情况下,引用计数不为0,于是造成father和son所指向的内存得不到释放,导致内存泄露。

4. 在多线程程序中使用shared_ptr应注意的问题。代码如下:

class tester
{
public:
  tester() {}
  ~tester() {}
  // 更多的函数定义…
};

void fun(boost::shared_ptr<tester> sp)
{
  // !!!在这大量使用sp指针.
  boost::shared_ptr<tester> tmp = sp;
}

int main()
{
  boost::shared_ptr<tester> sp1(new tester);
  // 开启两个线程,并将智能指针传入使用。
  boost::thread t1(boost::bind(&fun, sp1));
  boost::thread t2(boost::bind(&fun, sp1));
  t1.join();
  t2.join();
  return 0;
}

这个代码带来的问题很显然,由于多线程同是访问智能指针,并将其赋值到其它同类智能指针时,很可能发生两个线程同时在操作引用计数(但并不一定绝对发生),而导致计数失败或无效等情况,从而导致程序崩溃,如若不知根源,就无法查找这个bug,那就只能向上帝祈祷程序能正常运行。

可能一般情况下并不会写出上面这样的代码,但是下面这种代码与上面的代码同样,如下:

class tester
{
public:
  tester() {}
  ~tester() {}
public:
  boost::shared_ptr<int> m_spData; // 可能其它类型。
};

tester gObject;

void fun(void)
{
  // !!!在这大量使用sp指针.
  boost::shared_ptr<int> tmp = gObject.m_spData;
}

int main()
{
  // 多线程。
  boost::thread t1(&fun);
  boost::thread t2(&fun);
  t1.join();
  t2.join();
  return 0;
}

情况是一样的。要解决这类问题的办法也很简单,使用boost.weak_ptr就可以很方便解决这个问题。第一种情况修改代码如下:

class tester
{
public:
  tester() {}
  ~tester() {}
  // 更多的函数定义…
};

void fun(boost::weak_ptr<tester> wp)
{
  boost::shared_ptr<tester> sp = wp.lock;
  if (sp)
  {
    // 在这里可以安全的使用sp指针.
  }
  else
  {
    std::cout << “指针已被释放!” << std::endl;
  }
}

int main()
{
  boost::shared_ptr<tester> sp1(new tester);
  boost.weak_ptr<tester> wp(sp1);
  // 开启两个线程,并将智能指针传入使用。
  boost::thread t1(boost::bind(&fun, wp));
  boost::thread t2(boost::bind(&fun, wp));
  t1.join();
  t2.join();
  return 0;
}

boost.weak_ptr指针功能一点都不weak,weak_ptr是一种可构造、可赋值以不增加引用计数来管理shared_ptr的指针,它可以方便的转回到shared_ptr指针,使用weak_ptr.lock函数就可以得到一个shared_ptr的指针,如果该指针已经被其它地方释放,它则返回一个空的shared_ptr,也可以使用weak_ptr.expired()来判断一个指针是否被释放。

boost.weak_ptr不仅可以解决多线程访问带来的安全问题,而且还可以解决上面第三个问题循环引用。Children类代码修改如下,即可打破循环引用:

class child
{
public:
  ~child() {
   std::cout <<"子类析构函数被调用.\n";
  }
public:
  boost::weak_ptr<parent> parent;
};

因为boost::weak_ptr不增加引用计数,所以可以在退出函数域时,正确的析构。

程序中,有些对象的生命周期是静态的可预测的,这时只要把构造销毁的时机硬编码进程序即可;然而有些对象的生命周期却和运行时环境相关(例如在线程间共享对象,无“引用”时销毁),对于这种类型的对象必须借助额外的信息和设施来控制,基于引用计数的封装boost::shared_ptr及其相关设施就是其中一种(我所了解的还有基于标记-清除的GC),下面就易错的地方小结下:

  1. 一般而言,不要同时使用boost::shared_ptr和其他的管理机制来管理对象的生命周期,这样会发生语义上的矛盾,极有可能出现运行时错误。
  2. 切记保持boost::shared_ptr在程序各处的ownership语义,特别是对于线程函数以boost::shared_ptr持有某对象ownership的情况,必须保证在进入线程函数前完成引用计数的自增;因此,不进行有关ownership语义的的自增操作(一般而言,即以值语义直接传递boost::shared_ptr本身),而以指向或间接指向boost::shared_ptr的指针或引用作为线程函数的参数传递多数情况下都是畸形的语义,极有可能出现空指向的运行时错误,除非线程函数及其后继并不对boost::shared_ptr以及其指向对象作任何操作(这种情况下的传递是多余的)。
  3. 由于boost::shared_ptr享有所管理对象的ownership,因此一般来说对象不能享有指向自身的boost::shared_ptr对象的ownership;否则它们的销毁时机会形成彼此依赖的局面。
  4. 在一个对象的生命周期内,不要尝试多次从指向对象的原始指针构造boost::shared_ptr,这样会使每个boost::shared_ptr各自维护一个引用计数,造成对象的多次销毁;因此,在成员函数内使用this指针构造boost::shared_ptr基本上都是错误的,正确的方法是使此对象的类型继承boost::enable_shared_from_this,并调用成员函数shared_from_this得到boost::shared_ptr
  5. 对于继承于boost::enable_shared_from_this模板的类型,调用成员函数shared_from_this之前必须确保至少存在一个指向此类型对象的boost::shared_ptr实例,这是因为boost::enable_shared_from_this拥有一个boost::weak_ptr成员(为了避免第3项循环引用的情况),而boost::weak_ptr对象必须通过另一个boost::weak_ptrboost::shared_ptr来初始化。
  6. 由第5项可知:当类的某些成员需要以指向此类对象的boost::shared_ptr初始化时,不要尝试在构造函数中使用成员函数shared_from_this,此时并不能保证boost::shared_ptr的实例已经存在;取而代之,只能使用二段初始化流程,在构造函数之外对这些成员进行初始化;然而由于使用了shared_from_this,根据第1项,必须强制以boost::shared_ptr来管理此类对象的生命周期,也就是说,在构造函数返回时,必须立刻以其返回指针构造一个boost::shared_ptr;因此最好看以及最保险的方法是用一个静态初始化式(以指向此类对象的boost::shared_ptr作为返回值)包裹起二段初始化流程,隐藏实现。
  7. boost::shared_ptr本身对于同时读操作是线程安全的,同时写以及同时读写操作都不是线程安全的。
  8. boost::shared_ptr不改变其管理对象的线程安全级别。

posted on 2013-02-03 15:12  androidme  阅读(749)  评论(0编辑  收藏  举报

导航