boost智能指针之shared_ptr和weak_ptr

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;
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值