Monday, March 19, 2007

在你的代码中使用Boost智能指针(2)

 例子:在容器中使用shared_ptr
 
许多容器类,包括STL,都需要拷贝操作(例如,我们插入一个存在的元素到list,vector,或者container。)当拷贝操作是非常销毁资源的时候(这些操作时必须的),典型的操作就是使用容器指针。
std::vector<CMyLargeClass *> vec;
 
vec.push_back( new CMyLargeClass("bigString") );
 

 
 
将内存管理的任务抛给调用者,我们能够使用shared_ptr来实现。
typedef boost::shared_ptr<CMyLargeClass>  CMyLargeClassPtr;
 
std::vector<CMyLargeClassPtr> vec;
 
vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) );
 

 
 
当vector被销毁的时候,这个元素自动被销毁了。当然,除非有另一个智能指针引用了它,则还本能被销毁。让我们看Sample3中的使用:
void Sample3_Container()
 
{
 
  typedef boost::shared_ptr<CSample> CSamplePtr;
 
 
 
  // (A) create a container of CSample pointers:
 
  std::vector<CSamplePtr> vec;
 
 
 
  // (B) add three elements
 
  vec.push_back(CSamplePtr(new CSample));
 
  vec.push_back(CSamplePtr(new CSample));
 
  vec.push_back(CSamplePtr(new CSample));
 
 
 
  // (C) "keep" a pointer to the second:
 
  CSamplePtr anElement = vec[1];
 
 
 
  // (D) destroy the vector:
 
  vec.clear();
 
 
 
  // (E) the second element still exists
 
  anElement->Use();
 
  printf("done. cleanup is automatic\n");
 
 
 
  // (F) anElement goes out of scope, deleting the last CSample instance
 
}
 

 
 
6、 使用Boost中的智能指针,什么是正确的使用方法
 
使用智能指针的一些操作会产生错误(突出的事那些不可用的引用计数器,一些对象太容易释放,或者根本释放不掉)。Boost增强了这种安全性,处理了所有潜在存在的危险,所以我们要遵循以下几条规则使我们的代码更加安全。
 
下面几条规则是你应该必须遵守的:
 
规则一:赋值和保存 ―― 对于智能指针来说,赋值是立即创建一个实例,并且保存在那里。现在智能指针拥有一个对象,你不能手动释放它,或者取走它,这将帮助你避免意外地释放了一个对象,但你还在引用它,或者结束一个不可用的引用计数器。
 
规则二:_ptr<T> 不是T* ―― 恰当地说,不能盲目地将一个T* 和一个智能指针类型T相互转换。意思是:
 
・         当创建一个智能指针的时候需要明确写出 __ptr<T> myPtr<new T>。
 
・         不能将T*赋值给一个智能指针。
 
・         不能写ptr = NULL,应该使用ptr.reset()。
 
・         重新找回原始指针,使用ptr.get(),不必释放这个指针,智能指针会去释放、重置、赋值。使用get()仅仅通过函数指针来获取原始指针。
 
・         不能通过T*指向函数指针来代表一个__ptr<T>,需要明确构造一个智能指针,或者说将一个原始指针的所有权给一个指针指针。(见规则三)
 
・         这是一种特殊的方法来认定这个智能指针拥有的原始指针。不过在Boost:smart pointer programming techniques 举例说明了许多通用的情况。
 
规则三:非循环引用 ―― 如果有两个对象引用,而他们彼此都通过一个一个引用指针计数器,那么它们不能释放,Boost 提供了weak_ptr来打破这种循环引用(下面介绍)。
 
规则四:非临时的 share_ptr ―― 不能够造一个临时的share_ptr来指向它们的函数,应该命名一个局部变量来实现。(这可以使处理以外更安全,Boost share_ptr best practices 有详细解说)。
 
7、 循环引用
 
引用计数器是一种便利的资源管理机制,它有一个基本回收机制。但循环引用不能够自动回收,计算机很难检测到。一个最简单的例子,如下:
struct CDad;
 
struct CChild;
 
 
 
typedef boost::shared_ptr<CDad>   CDadPtr;
 
typedef boost::shared_ptr<CChild>  CChildPtr;
 
 
 
struct CDad : public CSample
 
{
 
   CChildPtr myBoy;
 
};
 
 
 
struct CChild : public CSample
 
{
 
 CDadPtr myDad;
 
};
 
 
 
// a "thing" that holds a smart pointer to another "thing":
 
 
 
CDadPtr   parent(new CDadPtr);
 
CChildPtr child(new CChildPtr);
 
 
 
// deliberately create a circular reference:
 
parent->myBoy = child;
 
child->myDad = dad;
 
 
 
// resetting one ptr...
 
child.reset();
 

         parent 仍然引用CDad对象,它自己本身又引用CChild。整个情况如下图所示:
 

如果我们调用dad.reset(),那么我们两个对象都会失去联系。但这种正确的离开这个引用,共享的指针看上去没有理由去释放那两个对象,我们不能够再访问那两个对象,但那两个对象的确还存在,这是一种非常严重的内存泄露。如果拥有更多的这种对象,那么将由更多的临界资源不能正常释放。
 
       如果不能解决好共享智能指针的这种操作,这将是一个严重的问题(至少是我们不可接受的)。因此我们需要打破这种循环引用,下面有三种方法:
 
A、   当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
 
B、   当Dad的生存期超过Child的生存期的时候,Child需要一个普通指针指向Dad。
 
C、  使用boost::weak_ptr打破这种循环引用。
 
方法A和B并不是一个完美的解决方案,但是可以在不使用weak_ptr的情况下让我们使用智能指针,让我们看看weak_ptr的详细情况。
 
 
 
8、 使用weak_ptr跳出循环
 
强引用和弱引用的比较:
 
一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。
 
boost::weak_ptr<T> 是执行弱引用的智能指针。当你需要它的时候就可以使用一个强(共享)指针指向它(当对象被释放的时候,它为空),当然这个强指针在使用完毕应该立即释放掉,在上面的例子中我们能够修改它为弱指针。
 
struct CBetterChild : public CSample
 
{
 
  weak_ptr<CDad> myDad;
 
 
 
  void BringBeer()
 
  {
 
    shared_ptr<CDad> strongDad = myDad.lock(); // request a strong pointer
 
    if (strongDad)                      // is the object still alive?
 
      strongDad->SetBeer();
 
    // strongDad is released when it goes out of scope.
 
    // the object retains the weak pointer
 
  }
 
};
 

 
 
9、 Intrusive_ptr――轻量级共享智能指针
 
shared_ptr比普通指针提供了更完善的功能。有一个小小的代价,那就是一个共享指针比普通指针占用更多的空间,每一个对象都有一个共享指针,这个指针有引用计数器以便于释放。但对于大多数实际情况,这些都是可以忽略不计的。
 
intrusive_ptr 提供了一个折中的解决方案。它提供了一个轻量级的引用计数器,但必须对象本身已经有了一个对象引用计数器。这并不是坏的想法,当你自己的设计的类中实现智能指针相同的工作,那么一定已经定义了一个引用计数器,这样只需要更少的内存,而且可以提高执行性能。
 
如果你要使用intrusive_ptr 指向类型T,那么你就需要定义两个函数:intrusive_ptr_add_ref 和intrusive_ptr_release。下面是一个简单的例子解释如何在自己的类中实现:
 
#include "boost/intrusive_ptr.hpp"
 
 
 
// forward declarations
 
class CRefCounted;
 
 
 
 
 
namespace boost
 
{
 
    void intrusive_ptr_add_ref(CRefCounted * p);
 
    void intrusive_ptr_release(CRefCounted * p);
 
};
 
 
 
// My Class
 
class CRefCounted
 
{
 
  private:
 
    long    references;
 
    friend void ::boost::intrusive_ptr_add_ref(CRefCounted * p);
 
    friend void ::boost::intrusive_ptr_release(CRefCounted * p);
 
 
 
  public:
 
    CRefCounted() : references(0) {}   // initialize references to 0
 
};
 
 
 
// class specific addref/release implementation
 
// the two function overloads must be in the boost namespace on most compilers:
 
namespace boost
 
{
 
 inline void intrusive_ptr_add_ref(CRefCounted * p)
 
  {
 
    // increment reference count of object *p
 
    ++(p->references);
 
  }
 
 
 
 
 
 
 
 inline void intrusive_ptr_release(CRefCounted * p)
 
  {
 
   // decrement reference count, and delete object when reference count reaches 0
 
   if (--(p->references) == 0)
 
     delete p;
 
  }
 
} // namespace boost
 

        
 
         这是一个最简单的(非线程安全)实现操作。但作为一种通用的操作,如果提供一种基类来完成这种操作或许很有使用价值,也许在其他地方会介绍到。
 
 
 
10、 scoped_array 和 shared_array
 
scoped_array 和 shared_array和上面讲的基本上相同,只不过他们是指向数组的。就像使用指针操作一样使用operator new[] ,他们都重载了operator new[]。注意他们并不初始化分配长度。
 
 
 
11、 Boost的安装
 
www.boost.org上下载最新版本的boost,然后解压缩到你指定的目录里,解压缩后的文件目录如下:
 
Boost\     boost的源文件和头文件。
 
Doc\        HTML格式的文档。
 
Lib\         库文件(不是必需的)
 
…             其他文件("more\"里有其他资料)
 
添加目录到我们自己的IDE里:
 
VC6:在菜单Tools/Options,Directories tab, "Show Directories for... Include files",
 
VC7: 在菜单Tools/Options,  Projects/VC++ directories, "Show Directories for... Include files".
 
Boost的头文件都在boost\子目录里,例如本文档例子中有#include "boost/smart_ptr.hpp"。所以任何人当读到年的源文件的时候就立刻知道你用到了boost中的智能指针。
 
 
 
12、 关于本文档中的例子
 
本文档中的例子里有一个子目录boost\仅仅包含了本例子中使用到的一些头文件,仅仅是为了你编译这个例子,如果你需要下载完整的boost或者获取更多的资源请到www.boost.org
 
 
 
13、 VC6中min/max的灾难
 
 
 
当在VC中使用boost库,或者其他库的时候会有一些小的问题。
 
在Windows的头文件中已经定义了min 和 max宏,所以在STL中的这两个函数就调用不到了,例如在MFC中就是这样,但是在Boost中,都是使用的std::命名空间下的函数,使用Windows的函数不能够接受不同类型的参数在模板中使用,但是许多库都依赖这些。
 
虽然Boost尽量处理这些问题,但有时候遇到这样的问题的时候就需要在自己的代码中加入像下面的代码在第一个#include前加入#define _NOMINMAX。
 
#define _NOMINMAX            // disable windows.h defining min and max as macros
 
#include "boost/config.hpp"  // include boosts compiler-specific "fixes"
 
using std::min;              // makle them globally available
 
using std::max;
 

         这样操作并不是在任何时候都需要,而只有我们碰到使用了就需要加入这段代码。
 
 
 
14、 资源
 
获取更多的信息,或者有问题可以查找如下资源:
 
・         Boost home page
 
・         Download Boost
 
・         Smart pointer overview
 
・         Boost users mailing list
 
・         Boost中的智能指针(撰文  Bjorn Karlsson    翻译  曾毅)
 

   郑重声明:
                 允许复制、修改、传递或其它行为
                 但不准用于任何商业用途.
                      写于  2004/11/5  masterlee
 
在你的代码中使用Boost智能指针(1)
 

No comments: