C++ Boost智能指针

Boost准标准库

boost是一个准标准库,相当于STL的延续和扩充,它的设计理念和STL比较接近,都是利用泛型让复用达到最大化。不过对比STL,boost更加实用。STL集中在算法部分,而boost包含了不少工具类,可以完成比较具体的工作。

 

    boost主要包含一下几个大类:字符串及文本处理、容器、迭代子(Iterator)、算法、函数对象和高阶编程、泛型编程、模板元编程、预处理元编程、并发编程、数学相关、纠错和测试、数据结构、输入/输出、跨语言支持、内存相关、语法分析、杂项。 有一些库是跨类别包含的,就是既属于这个类别又属于那个类别。

 

    在文本处理部分,conversion/lexcial_cast类用于“用C++”的方法实现数字类型和字符串之间的转换。 主要是替代C标准库中的 atoi、 itoa之类的函数。当然其中一个最大的好处就是支持泛型了。

 

    format库提供了对流的“printf-like”功能。printf里使用%d、%s等等的参数做替换的方法在很多情况下还是非常方便的,STL的iostream则缺乏这样的功能。format为stream增加了这个功能,并且功能比原始的printf更强。

 

    regex,这个不多说了,正则表达式库。如果需要做字符串分析的人就会理解正则表达式有多么有用了。

 

    spirit,这个是做LL分析的框架,可以根据EBNF规则对文件进行分析。(不要告诉我不知道什么是EBNF)。做编译器的可能会用到。一般人不太用的到。

 

    tokenizer库。我以前经常在CSDN上看到有人问怎么把一个字符串按逗号分割成字符串数组。也许有些人很羡慕VB的split函数。现在,boost的tokenizer也有相同的功能了,如果我没记错的话,这个tokenizer还支持正则表达式,是不是很爽?

 

    array: 提供了常量大小的数组的一个包装,喜欢用数组但是苦恼数组定位、确定数组大小等功能的人这下开心了。

 

    dynamic_bitset,动态分配大小的bitset,我们知道STL里有个bitset,为位运算提供了不少方便。可惜它的大小需要在编译期指定。现在好了,运行期动态分配大小的bitset来了。

 

    graph。提供了图的容器和相关算法。我还没有在程序中用到过图,需要用的人可以看看。

 

    multi_array提供了对多维数组的封装,应该还是比较有用的。

 

    并发编程里只有一个库,thread,提供了一个可移植的线程库,不过在Windows平台上我感觉用处不大。因为它是基于Posix线程的,在Windows里对Posix的支持不是很好。

 

    接下来的 数学和数值 类里,包含了很多数值处理方面的类库,数学类我也不太熟,不过这里有几个类还是很有用的,比如rational分数类,random随机数类,等等。

 

    static_assert,提供了编译器的assert功能。

 

    test库,一个单元测试框架,非常不错。

 

    concept_check提供了泛型编程时,对泛型量的一点检查,不是很完善,不过比没有好。

 

    数据类型类any,一个安全的可以包含不同对象的类。把它作为容器的元素类型,那么这个容器就可以包含不同类型的元素。比用void *要安全。

 

    compressed_pair,跟STL里的pair差不多。不过对空元素做了优化。

 

    tuple,呵呵,也许是某些人梦寐以求的东西。可以让函数返回多个值。

 

    跨语言支持:python,呵呵,好东东啊,可以将C++的类和函数映射给python使用。以下为几个CSDN上的关于boost.python的中文资料:http://dev.csdn.net/article/19/19828.shtm,http://dev.csdn.net/article/19/19829.shtm,http://dev.csdn.net/article/19/19830.shtm,http://dev.csdn.net/article/19/19831.shtm

    pool:内存池,呵呵,不用害怕频繁分配释放内存导致内存碎片,也不用自己辛辛苦苦自己实现了。

 

    smart_ptr:智能指针,这下不用担心内存泄漏的问题了吧。不过,C++里的智能指针都还不是十全十美的,用的时候小心点了,不要做太技巧性的操作了。

 

    date_time,这个是平台、类库无关的实现,如果程序需要跨平台,可以考虑用这个。

 

     timer,提供了一个计时器,虽然不是Windows里那种基于消息的计时器,不过据说可以用来测量语句执行时间。

 

     uitlity里提供了一个noncopyable类,可以实现“无法复制”的类。很多情况下,我们需要避免一个类被复制,比如代表文件句柄的类,文件句柄如果被两个实例共享,操作上会有很多问题,而且语义上也说不过去。一般的避免实例复制的方法是把拷贝构造和operator=私有化,现在只要继承一下这个类就可以了,清晰了很多。

 

     value_initialized:数值初始化,可以保证声明的对象都被明确的初始化,不过这个真的实用吗?似乎写这个比直接写初始化还累。呵呵,仁者见仁了。

 

     这里面除了regex、python和test需要编译出库才能用,其他的大部分都可以直接源代码应用,比较方便。其实这些库使用都不难。最主要的原因是有些库的使用需要有相关的背景知识,比如元编程、STL、泛型编程等等。

还有 Graph 库,用于图数据的处理

Boost智能指针

智能指针能够使C++的开发简单化,主要是它能够像其它限制性语言(如C#、VB)自动管理内存的释放,而且能够做更多的事情。

 

1、 什么是智能指针

智能指针是一种像指针的C++对象,但它能够在对象不使用的时候自己销毁掉。

我们知道在C++中的对象不再使用是很难定义的,因此C++中的资源管理是很复杂的。各种智能指针能够操作不同的情况。当然,智能指针能够在任务结束的时候删除对象,除了在程序之外。

许多库都提供了智能指针的操作,但都有自己的优点和缺点。Boost库是一个高质量的开源的C++模板库,很多人都考虑将其加入下一个C++标准库的版本中。

 

Boost提供了下面几种智能指针:

shared_ptr

本指针中有一个引用指针记数器,表示类型T的对象是否已经不再使用。shared_ptr Boost中提供普通的智能指针,大多数地方都使用shared_ptr

scoped_ptr

当离开作用域能够自动释放的指针。因为它是不传递所有权的。事实上它明确禁止任何想要这样做的企图!这在你需要确保指针任何时候只有一个拥有者时的任何一种情境下都是非常重要的。

intrusive_ptr

 shared_ptr 更好的智能指针,但是需要类型 T 提供自己的指针使用引用记数机制。

weak_ptr

一个弱指针,帮助shared_ptr 避免循环引用。

shared_array

 shared_ptr 类似,用来处理数组的。

scoped_array

 scoped_ptr 类似,用类处理数组的。

 

下面让我们看一个简单的例子:

 

2、 首先介绍:boost::scoped_ptr

scoped_ptr  Boost 提供的一个简单的智能指针,它能够保证在离开作用域后对象被释放。

例子说明:本例子使用了一个帮助我们理解的类: CSample在类的构造函数、赋值函数、析构函数中都加入了打印调试语句。因此在程序执行的每一步都会打印调试信息。在例子的目录里已经包含了程序中需要的Boost库的部分内容,不需要下载其它内容(查看Boost的安装指南)。

 

下面的例子就是使用scoped_ptr 指针来自动释放对象的:

使用普通指针

使用scoped_ptr 指针

void Sample1_Plain()

{

  CSample * pSample(new CSample);

 

  if (!pSample->Query() )

  // just some function...

  {

    delete pSample;

    return;

  }

 

  pSample->Use();

  delete pSample;

}

#include "boost/smart_ptr.h"

 

void Sample1_ScopedPtr()

{

  boost::scoped_ptr

       samplePtr(new CSample);

 

  if (!samplePtr->Query() )

  // just some function...

    return;   

 

  samplePtr->Use();

 

}

使用普通普通指针的时候,我们必须记住在函数退出的时候要释放在这个函数内创建的对象。当我们使用例外的时候处理指针是特别烦人的事情(容易忘记销毁它)。使用scoped_ptr 指针就能够在函数结束的时候自动销毁它,但对于函数外创建的指针就无能为力了。

优点:对于在复杂的函数种,使用scoped_ptr 指针能够帮助我们处理那些容易忘记释放的对象。也因此在调试模式下如果使用了空指针,就会出现一个断言。

 

优点

自动释放本地对象和成员变量[1],延迟实例化,操作PIMPL和RAII(看下面)

缺点

在STL容器里,多个指针操作一个对象的时候需要注意。

性能

使用scoped_ptr 指针,会增加一个普通指针。

 

3、 引用指针计数器

引用指针计数器记录有多少个引用指针指向同一个对象,如果最后一个引用指针被销毁的时候,那么就销毁对象本身。

shared_ptr 就是Boost中普通的引用指针计数器,它表示可以有多个指针指向同一个对象,看下面的例子:

void Sample2_Shared()

{

  // (A) 创建Csample类的一个实例和一个引用。

  boost::shared_ptr mySample(new CSample);

  printf("The Sample now has %i references\n", mySample.use_count()); // The Sample now has 1 references

  // (B) 付第二个指针给它。

  boost::shared_ptr mySample2 = mySample; // 现在是两个引用指针。

  printf("The Sample now has %i references\n", mySample.use_count());

 

  // (C) 设置第一个指针为空。

  mySample.reset();

  printf("The Sample now has %i references\n", mySample2.use_count());  // 一个引用

 

  // 当mySample2离开作用域的时候,对象只有一个引用的时候自动被删除。

}

 

在(A)中在堆栈重创建了CSample类的一个实例,并且分配了一个shared_ptr指针。对象mySample入下图所示:

然后我们分配了第二个指针mySample2,现在有两个指针访问同一个数据。

我们重置第一个指针(将mySample设置为空),程序中仍然有一个Csample实例,mySample2有一个引用指针。

只要当最有一个引用指针mySample2退出了它的作用域之外,Csample这个实例才被销毁。

当然,并不仅限于单个Csample这个实例,或者是两个指针,一个函数,下面是用shared_ptr的实例:

·         用作容器中。

·         用在PIMPL的惯用手法 (the pointer-to-implementation idiom )。

·         RAII(Resource-Acquisition-Is-Initialization)的惯用手法中。

·         执行分割接口。

注意:如果你没有听说过PIMPL (a.k.a. handle/body) 和 RAII,可以找一个好的C++书,在C++中处于重要的内容,一般C++程序员都应该知道(不过我就是第一次看到这个写法)。智能指针只是一中方便的他们的方法,本文中不讨论他们的内容。

PIMPL:如果必须包容一个可能抛异常的子对象,但仍然不想从你自己的构造函数中抛出异常,考虑使用被叫做Handle Class或Pimpl的方法(“Pimpl”个双关语:pImpl或“pointer to implementation”)

 

4、 主要特点

boost::shared_ptr 有一些重要的特征必须建立在其它操作之上。

·         shared_ptr作用在一个未知类型上

当声明或定义一个shared_ptr,T可能是一个未知的类型。例如你仅仅在前面声明了class T,但并没有定义class T。当我们要释放这个指针的时候我们需要知道这个T具体是一个声明类型。

·         shared_ptr作用在任意类型上

在这里本质上不需要制定T的类型(如从一个基类继承下来的)

·         shared_ptr支持自己定义释放对象的操作

如果你的类中自己写了释放方法,也可以使用。具体参照Boost文档。

·         强制转换

如果你定义了一个U*能够强制转换到T*(因为T是U的基类),那么shared_ptr也能够强制转换到shared_ptr

·         shared_ptr 是线程安全的

(这种设计的选择超过它的优点,在多线程情况下是非常必要的)

·         已经作为一种惯例,用在很多平台上,被证明和认同的。

 

5、 例子:在容器中使用shared_ptr

许多容器类,包括STL,都需要拷贝操作(例如,我们插入一个存在的元素到list,vector,或者container。)当拷贝操作是非常销毁资源的时候(这些操作时必须的),典型的操作就是使用容器指针。

std::vector vec;

vec.push_back( new CMyLargeClass("bigString") );

 

将内存管理的任务抛给调用者,我们能够使用shared_ptr来实现。

typedef boost::shared_ptr  CMyLargeClassPtr;

std::vector vec;

vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) );

 

vector被销毁的时候,这个元素自动被销毁了。当然,除非有另一个智能指针引用了它,则还本能被销毁。让我们看Sample3中的使用:

void Sample3_Container()

{

  typedef boost::shared_ptr CSamplePtr;

 

  // (A) create a container of CSample pointers:

  std::vector 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相互转换。意思是:

·         当创建一个智能指针的时候需要明确写出 __ptr myPtr

·         不能将T*赋值给一个智能指针。

·         不能写ptr = NULL,应该使用ptr.reset()

·         重新找回原始指针,使用ptr.get(),不必释放这个指针,智能指针会去释放、重置、赋值。使用get()仅仅通过函数指针来获取原始指针。

·         不能通过T*指向函数指针来代表一个__ptr,需要明确构造一个智能指针,或者说将一个原始指针的所有权给一个指针指针。(见规则三)

·         这是一种特殊的方法来认定这个智能指针拥有的原始指针。不过在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   CDadPtr;

typedef boost::shared_ptr  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打破这种循环引用。

方法AB并不是一个完美的解决方案,但是可以在不使用weak_ptr的情况下让我们使用智能指针,让我们看看weak_ptr的详细情况。

 

8、 使用weak_ptr跳出循环

强引用和弱引用的比较:

一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。

boost::weak_ptr 是执行弱引用的智能指针。当你需要它的时候就可以使用一个强(共享)指针指向它(当对象被释放的时候,它为空),当然这个强指针在使用完毕应该立即释放掉,在上面的例子中我们能够修改它为弱指针。

struct CBetterChild : public CSample

{

  weak_ptr myDad;

 

  void BringBeer()

  {

    shared_ptr 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、 VC6min/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中的智能指针

你可能感兴趣的:(C++,c++,boost,智能指针)