weak_ptr

优质
小牛编辑
139浏览
2023-12-01

头文件: "boost/weak_ptr.hpp"

weak_ptrshared_ptr 的观察员。它不会干扰shared_ptr所共享的所有权。当一个被weak_ptr所观察的 shared_ptr 要释放它的资源时,它会把相关的 weak_ptr的指针设为空。这防止了 weak_ptr 持有悬空的指针。你为什么会需要 weak_ptr? 许多情况下,你需要旁观或使用一个共享资源,但不接受所有权,如为了防止递归的依赖关系,你就要旁观一个共享资源而不能拥有所有权,或者为了避免悬空指针。可以从一个weak_ptr构造一个shared_ptr,从而取得对共享资源的访问权。

以下是 weak_ptr的部分定义,列出并简要介绍了最重要的函数。

namespace boost {

  template<typename T> class weak_ptr {
  public:
    template <typename Y>
      weak_ptr(const shared_ptr<Y>& r);

    weak_ptr(const weak_ptr& r);

    ~weak_ptr();

    T* get() const; 
    bool expired() const; 
    shared_ptr<T> lock() const;
  };  
}

成员函数

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

这个构造函数从一个shared_ptr创建 weak_ptr ,要求可以从 Y* 隐式转换为 T*. 新的 weak_ptr 被配置为旁观 r所引向的资源。r的引用计数不会有所改变。这意味着r所引向的资源在被删除时不会理睬是否有weak_ptr 引向它。这个构造函数不会抛出异常。

weak_ptr(const weak_ptr& r);

这个复制构造函数让新建的 weak_ptr 旁观与weak_ptr r<small class="calibre23">相关的shared_ptr</small>所引向的资源。<small class="calibre23">shared_ptr</small>的引用计数保持不变。这个构造函数不会抛出异常。

~weak_ptr();

weak_ptr 的析构函数,和构造函数一样,它不改变引用计数。如果需要,析构函数会把 *this 与共享资源脱离开。这个析构函数不会抛出异常。

bool expired() const;

如果所观察的资源已经"过期",即资源已被释放,则返回 True 。如果保存的指针为非空,expired 返回 false. 这个函数不会抛出异常。

shared_ptr<T> lock() const

返回一个引向weak_ptr所观察的资源的 shared_ptr ,如果可以的话。如果没有这样指针(即 weak_ptr 引向的是空指针),shared_ptr 也将引向空指针。否则,shared_ptr所引向的资源的引用计数将正常地递增。这个函数不会抛出异常。

用法

我们从一个示范weak_ptr的基本用法的例子开始,尤其要看看它是如何不影响引用计数的。这个例子里也包含了 shared_ptr,因为 weak_ptr 总是需要和 shared_ptr一起使用的。使用 weak_ptr 要包含头文件 "boost/weak_ptr.hpp".

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

class A {};

int main() {

  boost::weak_ptr<A> w;
  assert(w.expired());
  {
    boost::shared_ptr<A> p(new A());
    assert(p.use_count()==1);
    w=p;
    assert(p.use_count()==w.use_count());
    assert(p.use_count()==1);

    // 从weak_ptr创建shared_ptr 
    boost::shared_ptr<A> p2(w);
    assert(p2==p);
  }
  assert(w.expired());
  boost::shared_ptr<A> p3=w.lock();
  assert(!p3);
}

weak_ptr w 被缺省构造,意味着它初始时不旁观任何资源。要检测一个 weak_ptr 是否在旁观一个活的对象,你可以使用函数 expired. 要开始旁观,weak_ptr 必须要被赋值一个 shared_ptr. 本例中,shared_ptr p 被赋值给 weak_ptr w, 这等于说pw 的引用计数应该是相同的。然后,再从weak_ptr构造一个shared_ptr,这是一种从weak_ptr那里获得对共享资源的访问权的方法。如果在构造shared_ptr时,weak_ptr 已经过期了,将从shared_ptr的构造函数里抛出一个 boost::bad_weak_ptr 类型的异常。再继续,当 shared_ptr p 离开作用域,w 就变成过期的了。当调用它的成员函数 lock 来获得一个shared_ptr时,这是另一种获得对共享资源访问权的方法,将返回一个空的 shared_ptr 。注意,从这个程序的开始到结束,weak_ptr 都没有影响到共享对象的引用计数的值。

与其它智能指针不同的是,weak_ptr 不对它所观察的指针提供重载的 operator*operator-&gt;. 原因是对weak_ptr所观察的资源的任何操作都必须是明显的,这样才安全;由于不会影响它们所观察的共享资源的引用计数器,所以真的很容易就会不小心访问到一个无效的指针。这就是为什么你必须要传送 weak_ptrshared_ptr的构造函数,或者通过调用weak_ptr::lock来获得一个 shared_ptr 。这两种方法都会使引用计数增加,这样在 shared_ptrweak_ptr创建以后,它可以保证共享资源的生存,确保在我们要使用它的时候它不会被释放掉。

常见问题

由于在智能指针中保存的是指针的值而不是它们所指向的指针的值,因此在标准库容器中使用智能指针有一个常见的问题,就是如何在算法中使用智能指针;算法通常需要访问实际对象的值,而不是它们的地址。例如,你如何调用 std::sort 并正确地排序?实际上,这个问题与在容器中保存并操作普通指针是几乎一样的,但事实很容易被忽略(可能是由于我们总是避免在容器中保存裸指针)。当然我们 不能直接比较两个智能指针的值,但也很容易解决。只要用一个解引用智能指针的谓词就可以了,所以我们将创建一个可重用的谓词,使得可以在标准库的算法里使 用引向智能指针的迭代器,这里我们选用的智能指针是 weak_ptr

#include <functional>
#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"

template <typename Func, typename T> 
  struct weak_ptr_unary_t : 
    public std::unary_function<boost::weak_ptr<T>,bool> {
  T t_;
  Func func_;

  weak_ptr_unary_t(const Func& func,const T& t) 
    : t_(t),func_(func) {}

  bool operator()(boost::weak_ptr<T> arg) const {
    boost::shared_ptr<T> sp=arg.lock();
    if (!sp) {
      return false;
    }
    return func_(*sp,t_);
  }
};

template <typename Func, typename T> weak_ptr_unary_t<Func,T> 
  weak_ptr_unary(const Func& func, const T& value) {
    return weak_ptr_unary_t<Func,T>(func,value);
}

weak_ptr_unary_t 函数对象对要调用的函数以及函数所用的参数类型进行了参数化。把要调用的函数保存在函数对象中使用使得这个函数对象很容易使用,很快我们就能看到这一点。为了使这个谓词兼容于标准库的适配器,weak_ptr_unary_t 要从 std::unary_function派生,后者保证了所有需要的 typedefs 都能提供(这些要求是为了让标准库的适配器可以这些函数对象一起工作)。实际的工作在调用操作符函数中完成,从weak_ptr创建一个 shared_ptr 。必须要确保在函数调用时资源是可用的。然后才可以调用指定的函数(或函数对象),传入本次调用的参数(要解引用以获得真正的资源) 和在对象中保存的值,这个值是在构造weak_ptr_unary_t时给定的。这个简单的函数对象现在可以用于任意可用的算法了。为方便起见,我们还定义了一个助手函数,weak_ptr_unary, 它可以推出参数的类型并返回一个适当的函数对象[14]。我们来看看如何使用它。

[14] 要使得这个类型更通用,还需要更多的设计。

#include <iostream>
#include <string>

#include <vector>
#include <algorithm>
#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"

int main() {
  using std::string;
  using std::vector;
  using boost::shared_ptr;
  using boost::weak_ptr;

  vector<weak_ptr<string> > vec;

  shared_ptr<string> sp1(
    new string("An example"));
  shared_ptr<string> sp2(
    new string("of using"));
  shared_ptr<string> sp3(
    new string("smart pointers and predicates"));
  vec.push_back(weak_ptr<string>(sp1));
  vec.push_back(weak_ptr<string>(sp2));
  vec.push_back(weak_ptr<string>(sp3));

  vector<weak_ptr<string> >::iterator
    it=std::find_if(vec.begin(),vec.end(),
     weak_ptr_unary(std::equal_to<string>(),string("of using")));

  if (it!=vec.end()) {
    shared_ptr<string> sp(*++it);
    std::cout << *sp << '\n';
  }
}

本例中,创建了一个包含weak_ptrvector。最有趣的一行代码(是的,它有点长)就是我们为使用find_if算法而创建weak_ptr_unary_t的那行。

vector<weak_ptr<string> >::iterator it=std::find_if(
  vec.begin(),
  vec.end(),
  weak_ptr_unary(
    std::equal_to<string>(),string("of using")));

通过把另一个函数对象,std::equal_to, 和一个用于匹配的string一起传给助手函数weak_ptr_unary,创建了一个新的函数对象。由于 weak_ptr_unary_t 完全兼容于各种适配器(由于它是从std::unary_function派生而来的),我们可以再从它组合出各种各样的函数对象。例如,我们也可以查找第一个不匹配"of using"的串:

vector<weak_ptr<string> >::iterator it=std::find_if(
  vec.begin(),
  vec.end(),
std::not1(
    weak_ptr_unary(
      std::equal_to<string>(),string("of using"))));

Boost智能指针是专门为了与标准库配合工作而设计的。我们可以创建有用的组件来帮助我们可以更简单地使用这些强大的智能指针。象 weak_ptr_unary 这样的工具并不是经常要用到的;有一个库提供了比weak_ptr_unary更好用的泛型绑定器[15]。弄懂这些智能指针的语义,可以让我们更清楚地使用它们。

<a>[15] 指Boost.Bind库。

两种从weak_ptr创建shared_ptr的惯用法

如你所见,如果你有一个旁观某种资源的 weak_ptr ,你最终还是会想要访问这个资源。为此,weak_ptr 必须被转换为 shared_ptr, 因为 weak_ptr 是不允许访问资源的。有两种方法可以从weak_ptr创建shared_ptr:把 weak_ptr 传递给 shared_ptr 的构造函数,或者调用 weak_ptr 的成员函数lock, 它返回 shared_ptr. 选择哪一个取决于你认为一个空的 weak_ptr 是错误的抑或不是。shared_ptr 构造函数在接受一个空的 weak_ptr 参数时会抛出一个 bad_weak_ptr 类型的异常。因此应该在你认为空的 weak_ptr 是一种错误时使用它。如果使用 weak_ptr 的函数 lock, 它会在weak_ptr为空时返回一个空的 shared_ptr。这在你想测试一个资源是否有效时是正确的,一个空的 weak_ptr 是预期中的。此外,如果使用 lock, 那么使用资源的正确方法应该是初始化并同时测试它,如下:

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

int main() {
  boost::shared_ptr<std::string> 
    sp(new std::string("Some resource"));
  boost::weak_ptr<std::string> wp(sp);
  // ...
  if (boost::shared_ptr<std::string> p=wp.lock())
    std::cout << "Got it: " << *p << '\n';
  else
    std::cout << "Nah, the shared_ptr is empty\n";
}

如你所见,shared_ptr pweak_ptr wplock函数的结果初始化。然后 p 被测试,只有当它非空时资源才能被访问。由于 shared_ptr 仅在这个作用域中有效,所以在这个作用域之外不会有机会让你不小心用到它。另一种情形是当 weak_ptr 逻辑上必须非空的时候。那种情形下,不需要测试 shared_ptr 是否为空,因为 shared_ptr 的构造函数会在接受一个空weak_ptr时抛出异常,如下:

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

void access_the_resource(boost::weak_ptr<std::string> wp) {
  boost::shared_ptr<std::string> sp(wp);
  std::cout << *sp << '\n';
}

int main() {
  boost::shared_ptr<std::string> 
    sp(new std::string("Some resource"));
  boost::weak_ptr<std::string> wp(sp);
  // ...
  access_the_resource(wp);  
}

在这个例子中,函数 access_the_resource 从一个weak_ptr构造 shared_ptr sp 。这时不需要测试 shared_ptr 是否为空,因为如果 weak_ptr 为空,将会抛出一个 bad_weak_ptr 类型的异常,因此函数会立即结束;错误会在适当的时候被捕获和处理。这样做比显式地测试 shared_ptr 是否为空然后返回要更好。这就是从weak_ptr获得shared_ptr的两种方法。

总结

weak_ptr 是Boost智能指针拼图的最后一块。weak_ptr 概念是shared_ptr的一个重要伙伴。它允许我们打破递归的依赖关系。它还处理了关于悬空指针的一个常见问题。在共享一个资源时,它常用于那些不参与生存期管理的资源用户。这种情况不能使用裸指针,因为在最后一个 shared_ptr 被销毁时,它会释放掉共享的资源。如果使用裸指针来引用资源,将无法知道资源是否仍然存在。如果资源已经不存在,访问它将会引起灾难。通过使用 weak_ptr, 关于共享资源已被销毁的信息会传播给所有旁观的 weak_ptrs,这意味着不会发生无意间访问到无效指针的情形。这就象是观察员模式(Observer pattern)的一个特例;当资源被销毁,所有表示对此感兴趣的都会被通知到。

对于以下情形使用 weak_ptr

  • 要打破递归的依赖关系

  • 使用一个共享的资源而不需要共享所有权

  • 避免悬空的指针