boost智能指针之shared_ptr和weak_ptr

C++ 专栏收录该内容
6 篇文章 0 订阅
std::auto_ptr很多的时候并不能满足我们的要求,比如auto_ptr不能用作STL容器的元素。boost的smart_ptr中提供了4种智能指针和2种智能指针数组来作为std::auto_ptr的补充。  
shared_ptr<boost/shared_ptr.hpp>:使用shared_ptr进行对象的生存期自动管理,使得分享资源所有权变得有效且安全.
weak_ptr<boost/weak_ptr.hpp>:weak_ptr 是 shared_ptr 的观察员。它不会干扰shared_ptr所共享的所有权。 当一个被weak_ptr所观察的 shared_ptr 要释放它的资源时,它会把相关的 weak_ptr的指针设为空。使用此辅助指针一般是防止悬空指针。

Smart_ptr库如何改进你的程序
使用shared_ptr进行对象的生存期自动管理,使得分享资源所有权变得有效且安全。
使用weak_ptr可以安全地观测共享资源,避免了悬挂的指针。

智能指针解决了资源生存期管理的问题(尤其是动态分配的对象). 智能指针有各种不同的风格。多数都有一种共同的关键特性:自动资源管理。这种特性可能以不同的方式出现:如动态分配对象的生存期控制,和获取及释放资源 (文件, 网络连接)。Boost的智能指针主要针对第一种情况,它们保存指向动态分配对象的指针,并在正确的时候删除这些对象。你可能觉得奇怪为什么这些智能指针不多做一点工作。它们不可以很容易就覆盖所有资源管理的不同情况吗?是的,它们可以(在一定范围内它们可以),但不是没有代价的。通用的解决方案意味着更高的复杂性,而对于Boost的智能指针,可用性比灵活性具有更高的优先级。但是,通过对可定制删除器的支持,Boost的最智能的智能指针(boost::shared_ptr)可以支持那些不是使用delete进行析构的资源。

何时我们需要智能指针
  1. 资源所有权的共享:共享所有权是指两个或多个对象需要同时使用第三个对象的情况。这第三个对象应该如何(或者说何时)被释放?为了确保释放的时机是正确的,每个使用这个共享资源的对象必须互相知道对方,才能准确掌握资源的释放时间。从设计或维护的观点来看,这种耦合是不可行的。更好的方法是让这些资源所有者将资源的生存期管理责任委派给一个智能指针。当没有共享者存在时,智能指针就可以安全地释放这个资源了。
  2. 要编写异常安全的代码时:异常安全,简单地说就是在异常抛出时没有资源泄漏并保证程序状态的一致性。如果一个对象是动态分配的,当异常抛出时它不会自动被删除。由于栈展开以及指针离开作用域,资源可以会泄漏直至程序结束(即使是程序结束时的资源回收也不是由语言所保证的)。不仅可能程序会由于内存泄漏而耗尽资源,程序的状态也可能变得混乱。智能指针可以自动地为你释放这些资源,即使是在异常发生的情况下。
  3. 避免常见的错误,如资源泄漏:避免常见的错误。忘记调用 delete 是书本中最古老的错误(至少在这本书中)。一个智能指针不关心程序中的控制路径;它只关心在它所指向的对象的生存期结束时删除它。使用智能指针,你不再需要知道何时删除对象。并且,智能指针隐藏了释放资源的细节,因此使用者不需要知道是否要调用 delete, 有些特殊的清除函数并不总是删除资源的。
Smart_ptr如何适应标准库
Smart_ptr库已被提议包含进标准库中,Boost.Smart_ptr的 shared_ptr (以及随同的助手 enable_shared_from_this) 和 weak_ptr 已被收入即将发布的Library Technical Report。主要有以下三个原因:
  1. 标准库现在只提供了一个auto_ptr, 它仅是一类智能指针,仅仅覆盖了智能指针族谱中的一个部分。shared_ptr 提供了不同的,也是更重要的功能。
  2. Boost的智能指针专门为了与标准库良好合作而设计,并可作为标准库的自然扩充。例如,在 shared_ptr之前,还没有一个标准的智能指针可用作容器的元素。
  3. 现实世界中的程序员已经在他们的程序中大量使用这些智能指针类,它们已经得到了充分的验证。

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 ;否则返回 false。 unique 不会抛出异常。

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,它很好地确保了只有可适用的Boolean测试可以使用。这个函数不会抛出异常。

void swap(shared_ptr<T>& b);
这可以很方便地交换两个shared_ptr。swap 函数交换保存的指针(以及它们的引用计数)。这个函数不会抛出异常。

普通函数
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解决的主要问题是知道删除一个被多个客户共享的资源的正确时机。下面是一个简单易懂的例子,有两个类 A 和 B, 它们共享一个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);
}

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

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();
  }
}
这里有两个类, A 和 B, 各有一个虚拟成员函数 sing. B 从 A公有继承而来,并且如你所见,工厂函数 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用于某个特别的类型,它需要其它清除操作而不是简单的 delete. shared_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_ptr. weak_ptr 是 shared_ptr的一个观察者;它只是安静地坐着并看着它们,但不会影响引用计数。通过存储一个指向this的 weak_ptr 作为类的成员,就可以在需要的时候获得一个指向this的 shared_ptr。为了你可以不必编写代码来保存一个指向this的 weak_ptr,接着又从weak_ptr获shared_ptr得,Boost.Smart_ptr 为这个任务提供了一个助手类,称为 enable_shared_from_this. 只要简单地让你的类公有地派生自 enable_shared_from_this,然后在需要访问管理this的shared_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场景
  1. 当有多个使用者使用同一个对象,而没有一个明显的拥有者时
  2. 当要把指针存入标准库容器时
  3. 当要传送对象到库或从库获取对象,而没有明确的所有权时
  4. 当管理一些需要特殊清除方式的资源时(通过定制删除器的帮助)

shared_ptr陷阱

条款1: 不要把一个原生指针给多个shared_ptr管理
int* ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); //logic error
ptr对象被删除了2次,这种问题比喻成“二龙治水”,在原生指针中也同样可能发生。

条款2: 不要把this指针给shared_ptr
class Test{
public:
    void Do(){  m_sp =  shared_ptr<Test>(this);  }
private:
    shared_ptr<Test> m_member_sp;
};
Test* t = new Test;
shared_ptr<Test> local_sp(t);
p->Do();
t对象给了local_sp管理,然后在m_sp =  shared_ptr<Test>(this)这句里又请了一尊神来管理t。这就发生了条款1里“二龙治水”错误。

条款3: shared_ptr作为被保护的对象的成员时,小心因循环引用造成无法释放资源

对象需要相互协作,对象A需要知道对象B的地址,这样才能给对象B发消息(或调用其方法)。设计模式中有大量例子,一个对象中有其他对象的指针。现在把原生指针替换为shared_ptr.

假设a对象中含有一个shared_ptr<B>指向b对象;假设b对象中含有一个shared_ptr<A>指向a对象并且a,b对象都是堆中分配的。考虑某个shared_ptr<A> local_a;是我们能最后一个看到a对象的共享智能指针,其use_count==2,因为对象b中持有a的指针。所以当local_a说再见时,local_a只是把a对象的use_count改成1。同理b对象。然后我们再也看不到a,b的影子了,他们就静静的躺在堆里,成为断线的风筝。
解决方案是:Use weak_ptr to "break cycles."(boost文档里写的)或者显示的清理。

示例:
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

using namespace std;
using boost::shared_ptr;
using boost::weak_ptr;

class B;
class A;

class A
{
        public:
                A(){cout << "A constructor~~" << endl;}
                shared_ptr<B> m_b;
                ~A(){cout << "A deconstructor~~" << endl;}
};
class B
{
        public:
                B(){cout << "B constructor~~" << endl;}
                shared_ptr<A> m_a;
                ~B(){cout << "B deconstructor~~" << endl;}
};

class WA;
class WB;

class WA
{
        public:
                WA(){cout << "WA constructor~~" << endl;}
                weak_ptr<WB> m_b;
                ~WA(){cout << "WA deconstructor~~" << endl;}
};
class WB
{
        public:
                WB(){cout << "WB constructor~~" << endl;}
                weak_ptr<WA> m_a;
                ~WB(){cout << "WB deconstructor~~" << endl;}
};
int main()
{
        do
        {
                shared_ptr<A> aptr(new A);
                shared_ptr<B> bptr(new B);
                aptr->m_b = bptr;
                bptr->m_a = aptr;
        }while(0);
        do
        {
                shared_ptr<WA> waptr(new WA);
                shared_ptr<WB> wbptr(new WB);
                waptr->m_b = wbptr;
                wbptr->m_a = waptr;
        }while(0);
}
执行结果:
A constructor~~
B constructor~~
WA constructor~~
WB constructor~~
WB deconstructor~~
WA deconstructor~~
可以看到,采用shared_ptr相互引用时,没有执行析构函数,也就是说申请的内存没有被释放;而采用weak_ptr相互引用可以正常执行析构函数,即释放了内存。

所以在使用基于引用计数的智能指针时,要特别小心循环引用带来的内存泄漏,循环引用不只是两方的情况,只要引用链成环都会出现问题。当然循环引用本身就说明设计上可能存在一些问题,如果特殊原因不得不使用循环引用,那可以让引用链上的一方持用普通指针(或弱智能指针weak_ptr)即可。

条款4: 不要在函数实参里创建shared_ptr
function ( shared_ptr<int>(new int), g( ) );  //有缺陷
可能的过程是先new int,然后调g( ),g( )发生异常,shared_ptr<int>没有创建,int内存泄露

shared_ptr<int> p(new int());
f(p, g());  //Boost推荐写法

条款5: 对象内部生成shared_ptr
不能把this指针直接扔给shared_ptr. 但是没有禁止在对象内部生成自己的shared_ptr
//这是Boost的例子改的。
class Y: public boost::enable_shared_from_this<Y>
{
    boost::shared_ptr<Y> GetSelf()
    {
        return shared_from_this();
    }
};
普通的(没有继承enable_shared_from_this)类T的shared_ptr<T> p(new T),p作为栈对象占8个字节,为了记录(new T)对象的引用计数,p会在堆上分配16个字节以保存引用计数等“智能信息”。share_ptr没有“嵌入(intrusive)”到T对象,或者说T对象对share_ptr毫不知情。Y对象则不同,Y对象已经被“嵌入”了一些share_ptr相关的信息,目的是为了找到“全局性”的那16字节的本对象的“智能信息”。原理说完了,就是陷阱:
Y y;
boost::shared_ptr<Y> p=  y.GetSelf(); //无知的代码,y根本就不是new出来的

Y* y = new Y;
boost::shared_ptr<Y> p=  y->GetSelf(); //似是而非,仍旧程序崩盘。
Boost文档说, 在调用shared_from_this()之前,必须存在一个正常途径创建的shared_ptr

boost::shared_ptr<Y> spy(new Y)
boost::shared_ptr<Y> p =  spy->GetSelf(); //OK

条款6 : 处理不是new的对象要小心
int* pi = (int*)malloc(4)
shared_ptr<int> sp( pi ) ; //delete马嘴不对malloc驴头。

条款7: 多线程对引用计数的影响
如果是轻量级的锁,比如InterLockIncrement等,对程序影响不大;如果是重量级的锁,就要考虑因为share_ptr维护引用计数而造成的上下文切换开销。1.33版本以后的shared_ptr对引用计数的操作使用的是Lock-Free(类似InterLockIncrement函数族)的操作,应该效率不错,而且能保证线程安全(库必须保证其安全,程序员都没有干预这些隐藏事物的机会)。 Boost文档说read,write同时对shared_ptr操作时,行为不确定。这是因为shared_ptr本身有两个成员px,pi。多线程同时对px读写是要出问题的。与一个int的全局变量多线程读写会出问题的原因一样。

boost为shared_ptr提供了与内置类型同级别的线程安全性,这包括:
1. 同一个shared_ptr对象可以被多线程同时读取。
2. 不同的shared_ptr对象可以被多线程同时修改。
3. 同一个shared_ptr对象不能被多线程直接修改,但可以通过原子函数完成。
如果把上面的表述中的"shared_ptr"替换为“内置类型”也完全成立。

条款8: 对象数组用shared_array
int* pint = new int[100];
shared_array<int> p (pint );
既然shared_ptr对应着delete;显然需要一个delete[]对应物shared_array

条款9: 学会用删除器
struct Test_Deleter
{  
    void  operator ()( Test* p){   ::free(p);   }
};
Test* t = (Test*)malloc(sizeof(Test));
new (t) Test;
shared_ptr<Test> sp( t ,  Test_Deleter() ); //删除器可以改变share_ptr销毁对象行为
有了删除器,shared_array无用武之地了。
template<class T>
struct Array_Deleter
{  
    void  operator ()( T*){   delete[] p;   }
};
int* pint = new int[100];
shared_ptr<int> p (pint, Array_Deleter<int>() );

条款10: 学会用分配器
存放引用计数的地方是堆内存,需要16-20字节的开销。如果大量使用shared_ptr会造成大量内存碎片。shared_ptr构造函数的第3个参数是分配器,可以解决这个问题。
shared_ptr<Test> p( (new Test), Test_Deleter(), Mallocator<Test>() );
注意删除器Test_Deleter是针对Test类的。分配器是针对shared_ptr内部数据的;Mallocator<Test>()是个临时对象(无状态的),符合STL分配器规约。
template <typename T>
class Mallocator {
    //。。。。。。
    T * allocate(const size_t n) const {
        return singleton_pool<T,sizeof(T)>::malloc();
    }
    //。。。。。。
Mallocator传入Test,实际分配的类型却是
class boost::detail::sp_counted_impl_pda<class Test *,
                                         struct Test_Deleter,
                                         class Mallocator<class Test> >
这是用typeid(T).name()打印出来的,可能和rebind相关。

条款11  weak_ptr在使用前需要检查合法性
weak_ptr<K> wp;
{
shared_ptr<K>  sp(new K);  //sp.use_count()==1
wp = sp; //wp不会改变引用计数,所以sp.use_count()==1
shared_ptr<K> sp_ok = wp.lock(); //wp没有重载->操作符,只能这样取所指向的对象
}
shared_ptr<K> sp_null = wp.lock(); //sp_null .use_count()==0;
因为上述代码中sp和sp_ok离开了作用域,其容纳的K对象已经被释放了,得到了一个容纳NULL指针的sp_null对象;在使用wp前需要调用wp.expired()函数判断一下。因为wp仍旧存在,虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息。直到最后一个weak_ptr对象被析构,这块“堆”存储块才能被回收。否则weak_ptr无法知道自己所容纳的那个指针资源的当前状态。

条款12  不要new shared_ptr<T>
本来shared_ptr就是为了管理指针资源的,不要又引入一个需要管理的指针资源shared_ptr<T>*

条款13   尽量不要get
class B{...};
class D : public B{ ...};  //继承层次关系
shared_ptr<B> sp (new D);    //通过隐式转换,储存D的指针。
B* b = sp.get();             //shared_ptr辛辛苦苦隐藏的原生指针就这么被刨出来了。
D* d = dynamic_cast<D*>(b);  //这是使用get的正当理由吗?
正确的做法
shared_ptr<B> spb (new D)  ;
shared_ptr<D> spd =  shared_dynamic_cast<D>(spb); //变成子类的指针
shared_ptr在竭尽全力表演的像一个原生指针,原生指针能干的事,它也基本上能干。

另一个同get相关的错误
shared_ptr<T> sp(new T);
shared_ptr<T> sp2( sp.get() ) ;//又一个“二龙治水”实例,指针会删2次而错误。

条款14  不要memcpy shared_ptr
shared_ptr<B> sp1 (new B)  ;
shared_ptr<B> sp2;
memcpy(&sp2,&sp1,sizeof(shared_ptr<B>)); //sp2.use_count()==1
很显然,不是通过正常途径(拷贝构造,赋值运算),引用计数是不会正确增长的。

条款15  使用BOOST预定义的宏去改变shared_ptr行为
shared_ptr行为由类似BOOST_SP_DISABLE_THREADS这样的宏控制。需要去学习他们到底是干什么的。大师Andrei Alexandrescu设计了一种基于模板策略设计模式的智能指针,通过几个模板参数去定制化智能指针的行为。Boost却不以为然,官方解释是:需要统一的接口,这样利于大规模书写。smart_ptr<T,OwnershipPolicy,ConversionPolicy,CheckingPolicy,StoragePolicy> sp(new T);上述接口缺点是外形复杂,看上去像个大花脸。优点是客户程序员可以轻易的定制行为。

条款16  构造函数里调用shared_from_this会抛出异常
class Holder:public enable_shared_from_this<Holder>{
public:
    Holder() {
        shared_ptr<Holder> sp = shared_from_this();
        int x = sp.use_count();
    }
};
同前面条款5,不符合enable_shared_from_this使用前提。

shared_array
shared_array 用于共享数组所有权的智能指针。它与shared_ptr的关系就如scoped_array与scoped_ptr的关系。shared_array 与 shared_ptr 的不同之处主要在于它是用于数组的而不是用于单个对象的。在我们讨论 scoped_array时,我提到过通常std::vector是一个更好的选择。但 shared_array 比 vector更有价值,因为它提供了对数组所有权的共享。shared_array 的接口与 shared_ptr非常相似,差别仅在于增加了一个下标操作符,以及不支持定制删除器。

由于一个指向std::vector的shared_ptr提供了比shared_array更多的灵活性,所以我们就不对shared_array的用法进行讨论了。

weak_ptr

弱指针只有在配合共享指针使用时才会有意义。boost::weak_ptr总是通过boost::shared_ptr来初始化的,一旦初始化之后,它基本上只提供一个有用的方法: lock()。此方法返回的boost::shared_ptr与用来初始化弱指针的共享指针共享所有权。 如果这个共享指针不含有任何对象,返回的共享指针也将是空的。
当函数需要一个由共享指针所管理的对象,而这个对象的生存期又不依赖于这个函数时,就可以使用弱指针。 只要程序中还有一个共享指针掌管着这个对象,函数就可以使用该对象。 如果共享指针复位了,就算函数里能得到一个共享指针,对象也不存在了。
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>

using std::cout;
using std::endl;

using boost::shared_ptr;
using boost::weak_ptr;
using boost::thread;

void resetobj(shared_ptr<int> *sptr)
{
        sptr->reset();
        return;
}

void printobj(weak_ptr<int> *wptr)
{
        shared_ptr<int> sh = wptr->lock();
        if(sh != NULL)
                cout << "weak ptr get contens: " << *sh << endl;
        else
                cout << "weak ptr get empty contents." << endl;
        return;
}

int main()
{
        shared_ptr<int> sh(new int(99));
        weak_ptr<int> w(sh);

        thread sthobj(bind(resetobj, &sh));
        thread wthobj(bind(printobj, &w));

        sthobj.join();
        wthobj.join();
        cout << "finished~~~" << endl;
}
执行结果:由于线程执行顺序的不确定性,printobj的打印信息不确定。

第一个线程函数resetobj的参数是一个共享指针的地址。第二个线程函数printobj的参数是一个弱指针的地址。这个弱指针是之前通过共享指针初始化的。一旦程序启动之后,resetobj和 printobj就都开始执行了。不过执行顺序是不确定的,这就导致了一个潜在的问题:resetobj线程在销毁对象的时候printobj线程可能正在访问它。这时就可以通过调用弱指针的lock() 函数解决这个问题:如果对象存在,那么lock()函数返回的共享指针指向这个合法的对象。否则,返回的共享指针被设置为0,这等价于标准的null指针。弱指针本身对于对象的生存期没有任何影响。lock返回一个共享指针,printobj函数就可以安全的访问对象了。这就保证了即使另一个线程要释放对象,由于我们有返回的共享指针,对象依然存在。

注:当函数为void resetobj(shared_ptr<int> sptr)和void printobj(weak_ptr<int> wptr)时,printobj打印一直为99,为传入的整数值,why??

weak_ptr
weak_ptr 是 shared_ptr 的观察员。它不会干扰shared_ptr所共享的所有权。当一个被weak_ptr所观察的 shared_ptr 要释放它的资源时,它会把相关的 weak_ptr的指针设为空。这防止了 weak_ptr 持有悬空的指针。你为什么会需要 weak_ptr? 许多情况下,你需要旁观或使用一个共享资源,但不接受所有权,如为了防止递归的依赖关系,你就要旁观一个共享资源而不能拥有所有权,或者为了避免悬空指针。可以从一个weak_ptr构造一个shared_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相关的shared_ptr所引向的资源。shared_ptr的引用计数保持不变。这个构造函数不会抛出异常。

~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 ,你最终还是会想要访问这个资源。为此,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。
#include <boost/weak_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <cassert>

using boost::weak_ptr;
using boost::shared_ptr;

class A{};

int main()
{
        weak_ptr<A> w;
        assert((w.expired()));
        {
                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);

                shared_ptr<A> p2(w);
                assert(p2 == p);
                assert(p.use_count() == 2);
        }
        assert(w.expired());
        shared_ptr<A> p3 = w.lock();
        assert(p3 == NULL);
}
weak_ptr w 被缺省构造,意味着它初始时不旁观任何资源。要检测一个 weak_ptr 是否在旁观一个活的对象,你可以使用函数 expired. 要开始旁观,weak_ptr 必须要被赋值一个 shared_ptr. 本例中,shared_ptr p 被赋值给 weak_ptr w, 这等于说p 和 w 的引用计数应该是相同的。然后,再从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->. 原因是对weak_ptr所观察的资源的任何操作都必须是明显的,这样才安全;由于不会影响它们所观察的共享资源的引用计数器,所以真的很容易就会不小心访问到一个无效的指针。这就是为什么你必须要传送 weak_ptr 给 shared_ptr的构造函数,或者通过调用weak_ptr::lock来获得一个 shared_ptr 。这两种方法都会使引用计数增加,这样在 shared_ptr 从 weak_ptr创建以后,它可以保证共享资源的生存,确保在我们要使用它的时候它不会被释放掉。

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

#include <functional>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>

using boost::weak_ptr;
using boost::shared_ptr;
using std::unary_function;
using std::equal_to;
using std::vector;
using std::string;
using std::cout;
using std::endl;

template <typename Func, typename T>
struct weak_ptr_unary_t : public unary_function<weak_ptr<T>, bool>
{
        T m_t;
        Func m_func;
        weak_ptr_unary_t(const Func& func, const T& t):m_t(t), m_func(func){}
        bool operator()(weak_ptr<T> arg)const
        {
                shared_ptr<T> sp = arg.lock();
                if(sp == NULL)
                        return false;
                else
                        return m_func(*sp, m_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);
}

int main()
{
        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 = find_if(vec.begin(), vec.end(), weak_ptr_unary(equal_to<string>(), string("of using")));
        if(it != vec.end())
        {
                shared_ptr<string> sp(*it++);
                cout << "find : " << *sp << endl;
        }
        it = find_if(vec.begin(), vec.end(), not1(weak_ptr_unary(equal_to<string>(), string("of using"))));
        if(it != vec.end())
        {
                shared_ptr<string> sp(*it++);
                cout << "the first not equal : " << *sp << endl;
        }
}
执行结果:
find : of using
the first not equal : An example

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

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

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

对于以下情形使用 weak_ptr
  1. 要打破递归的依赖关系
  2. 使用一个共享的资源而不需要共享所有权
  3. 避免悬空的指针
  • 1
    点赞
  • 1
    评论
  • 7
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论 1 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

IT_Linux

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值