C/C++ 开发 boost 库参考手册整理(1) 【学习笔记】

文档声明:
以下资料均属于本人在学习过程中产出的学习笔记,如果错误或者遗漏之处,请多多指正。并且该文档在后期会随着学习的深入不断补充完善。感谢各位的参考查看。


笔记资料仅供学习交流使用,转载请标明出处,谢谢配合。
如果存在相关知识点的遗漏,可以在评论区留言,看到后将在第一时间更新。
作者:Aliven888

导言

  本文档资料是根据官网文档库整理而出的。因篇幅有限,前半部分文档请跳转《C/C++ 开发 boost 库参考手册整理(2) 【学习笔记】》查看。

第 1 章 简介

1.1. C++ 与 Boost

  Boost C++ 库 是一组基于C++标准的现代库。 其源码按 Boost Software License 来发布,允许任何人自由地使用、修改和分发。 这些库是平台独立的,且支持大多数知名和不那么知名的编译器。

  Boost 社区负责开发和发布 Boost C++ 库。 社区由一个很大的C++开发人员群组组成,这些开发人员来自于全球,他们通过网站 www.boost.org 以及几个邮件列表相互协调。 社区的使命是开发和收集高质量的库,作为C++标准的补充。 那些被证实有价值且对于C++应用开发非常重要的库,将会有很大机会在某天被纳入C++标准中。

  Boost 社区在1998年左右出现,当时刚刚发布了C++标准的第一个版本。 从那时起,社区就不断地扩大,现在已成为C++标准化工作中的一个重要角色。 虽然 Boost 社区与标准化委员会之间没有直接的关系,但有部分开发者同时活跃于两方。 下一个版本的C++标准很大可能在2011年通过,其中将扩展一批库,这些库均起源于 Boost 社区。

  要增强C++项目的生产力,除了C++标准以外,Boost C++ 库是一个不错的选择。 由于当前版本的C++标准在2003年修订之后,C++又有了新的发展,所以 Boost C++ 库提供了许多新的特性。 由于有了 Boost C++ 库,我们无需等待下一个版本的C++标准,就可以立即享用C++演化中取得的最新进展。

  Boost C++ 库具有良好的声誉,这基于它们的使用已被证实是非常有价值的。 在面试中询问关于 Boost C++ 库的知识是不常见的,因为知道这些库的开发人员通常也清楚C++的最新创新,并且能够编写和理解现代的C++代码。

1.2. 开发过程

  正是因为大量的独立开发者和组织的支持和参与,才使用 Boost C++ 库的开发成为可能。 由于 Boost 只接受满足以下条件的库:解决了真实存在的问题、表现出令人信服的设计、使用现代C++来开发且以可理解的方式提供文档,所以每一个 Boost C++ 库的背后都有大量的工作。

  C++ 开发者都可以加入到 Boost 社区中,并提出自己的新库。 但是,要将一个想法变成一个 Boost C++ 库,需要投入大量的时间和努力。 其中最重要的是在 Boost 邮件列表中与其他开发者及潜在用户讨论需求和可能的解决方案。

  除了这些好象不知从何处冒出来的新库以外,也可以提名一些已有的 C++ 库进入 Boost。 不过,由于对这些库的要求是与专门为 Boost 开发的库一样的,所以可能需要对它们进行大量的修改。

  一个库是否被接纳入 Boost,取决于评审过程的结果。 库的开发者可以申请评审,这通常需要10天的时间。 在这段时间内,其他开发者被邀请对这个库进行评分。 基于正面和负面评价的数量,评审经理将决定该库是否被接纳进入 Boost。 由于有些开发者是在评审阶段才首次公开库的代码,所以在评审期间被要求对库进行修改并不罕见。

  如果一个库是因为技术原因被拒绝,那么它还有可能在修改之后对更新后的版本申请新的评审。 但是,如果一个库是因为不能解决实际问题或未能提供令人信服的解决方案而被拒绝,那么再一次评审也很可能会被拒绝。

  因为可能随时接纳新的库,所以 Boost C++ 库会每三个月发布一次新版本。本书所涉及的库均基于2010年2月发布的 1.42.0 版本。

  请注意,另外还有一些库已被接纳,但尚未成为 Boost C++ 库发布版的一部分。在被包含进发布版之前,它们必须手工安装。

1.3. 安装

  Boost C++ 库均带有源代码。其中大多数库只包含头文件,可以直接使用,但也有一些库需要编译。 为了尽可能容易安装,可以使用 Boost Jam 进行自动安装。 无需逐个库进行检查和编译,Boost Jam 自动安装整个库集。 它支持许多操作系统和编译器,并且知道如何基于适当的配置文件来编译单个库。

  为了在 Boost Jam 的帮助下自动安装,要使用一个名为 bjam 的应用程序,它也带有源代码。 对于某些操作系统,包括 Windows 和 Linux,也有预编译好的 bjam 二进制文件。

  为了编译 bjam 本身,要执行一个名为 build 的简单脚本,它也为不同的操作系统提供了源代码。 对于 Windows,它是批处理文件 build.bat。 对于 Linux,文件名为 build.sh。

  如果执行 build 时不带任何命令行选项,则该脚本尝试找到一个合适的编译器来生成 bjam。 通过使用命令行开关,称为 toolset,可以选择特定的编译器。 对于 Windows,build 支持 toolsets vc7, vc8 和 vc9,可以选择不同版本的 Microsoft C++ 编译器。 要从 Visual Studio 2008 的C++编译器编译 bjam,需要指定命令 build vc9。对于 Linux,支持 toolsets gcc 和 intel-linux,分别选定 GCC 和 Intel 的C++编译器。

  应用程序 bjam 必须复制到本地的 Boost 目录 - 不论它是编译出来的还是下载的预编译二进制文件。 然后就可以不带任何命令行选项地执行 bjam,编译并安装 Boost C++ 库。 由于缺省选项 - 在这种情况下所使用的 - 并不一定是最好的选择,所以以下列出最重要的几个选项供参考:

  声明 stage 或 install 可以指定 Boost C++ 库是安装在一个名为 stage 的子目录下,还是在系统范围内安装。 系统范围 的意义取决于操作系统。 在 Windows 中,目标目录是 C:\Boost;而在 Linux 中则是 /usr/local。 目标目录也可以用 --prefix 选项明确指出。

  如果不带任何命令行选项执行 bjam,则它会自己搜索一个合适的C++编译器。 可以通过 --toolset 选项来指定一个特定的编译器。 要在 Windows 中指定 Visual Studio 2008 的 Microsoft C++ 编译器,bjam 执行时要带上 --toolset=msvc-9.0 选项。 要在 Linux 中指定 GCC 编译器,则要给出 --toolset=gcc 选项。

  命令行选项 --build-type 决定了创建何种方式的库。 缺省情况下,该选项设为 minimal,即只创建发布版。 对于那些想用 Visual Studio 或 GCC 构建他们项目的调试版的开发者来说,可能是一个问题。 因为这些编译器会自动尝试链接调试版的 Boost C++ 库,这样就会给出一个错误信息。 在这种情况下,应将 --build-type 选项设为 complete,以同时生成 Boost C++ 库的调试版和发布版,当然,所需时间也会更长一些。

  要用 Visual Studio 2008 的C++编译器同时生成 Boost C++ 库的调试版和发布版,并将它们安装在目录 D:\Boost 中,应执行的命令是 bjam --toolset=msvc-9.0 --build-type=complete --prefix=D:\Boost install. 要在 Linux 中使用缺省目录创建它们,则要执行的命令是 bjam --toolset=gcc --build-type=complete install.

  其它多个命令行选项可用于指定如何编译 Boost C++ 库的一些细节设定。 我通常在 Windows 下使用以下命令:bjam --toolset=msvc-9.0 debug release link=static runtime-link=shared install. debug 和 release 使得调试版和发布版均被生成。 link=static 则只创建静态库。 runtime-link=shared 则是指定 C++ 运行时库是动态链接的,这是在 Visual Studio 2008 中对C++项目的缺省设置。

1.4. 概述

  Boost C++ 库的 1.42.0 版本包含了超过90个库,本书只详细讨论了以下各库:

Boost C++ 库 C++ 标准 简要说明
Boost.Any Boost.Any 提供了一个名为 boost::any 的数据类型,可以存放任意的类型。 例如,一个类型为 boost::any 的变量可以先存放一个 int 类型的值,然后替换为一个 std::string 类型的字符串。
Boost.Array TR1 Boost.Array 可以把 C++ 数组视同 C++ 标准的容器。
Boost.Asio TR2 Boost.Asio 可用于开发异步处理数据的应用,如网络应用。
Boost.Bimap Boost.Bimap 提供了一个名为 boost::bimap 的类,它类似于 std::map. 主要的差别在于 boost::bimap 可以同时从键和值进行搜索。
Boost.Bind TR1 Boost.Bind 是一种适配器,可以将函数作为模板参数,即使该函数的签名与模板参数不兼容。
Boost.Conversion Boost.Conversion 提供了三个转型操作符,分别执行向下转型、交叉转型,以及不同数字类型间的值转换。
Boost.DateTime Boost.DateTime 可用于以灵活的格式处理、读入和写出日期及时间值。
Boost.Exception Boost.Exception 可以在抛出的异常中加入额外的数据,以便在 catch 处理中提供更多的信息。 这有助于更容易地调试,以及对异常情况更好地作出反应。
Boost.Filesystem TR2 Boost.Filesystem 提供了一个类来处理路径信息,还包含了几个访问文件和目录的函数。
Boost.Format Boost.Format 以一个类型安全且可扩展的 boost::format 类替代了 std::printf() 函数。
Boost.Function TR1 Boost.Function 简化了函数指针的定义。
Boost.Interprocess Boost.Interprocess 允许多个应用通过共享内存以快速、高效的方式进行通信。
Boost.Lambda Boost.Lambda 可以定义匿名的函数。 代码被内联地声明和执行,避免了单独的函数调用。
Boost.Multiindex Boost.Multiindex 定义了一些新的容器,它们可以同时支持多个接口,如 std::vector 和 std::map 的接口。
Boost.NumericConversion Boost.NumericConversion 提供了一个转型操作符,可以安全地在不同的数字类型间进行值转换,不会生成上溢出或下溢出的条件。
Boost.PointerContainer Boost.PointerContainer 提供了专门为动态分配对象进行优化的容器。
Boost.Ref TR1 Boost.Ref 的适配器可以将不可复制对象的引用传给需要复制的函数。
Boost.Regex TR1 Boost.Regex 提供了通过正则表达式进行文本搜索的函数。
Boost.Serialization 通过 Boost.Serialization,对象可以被序列化,如保存在文件中,并在以后重新导入。
Boost.Signals Boost.Signal 是一个事件处理的框架,基于所谓的 signal/slot 概念。 函数与信号相关联并在信号被触发时自动被调用。
Boost.SmartPoiners TR1 Boost.SmartPoiners 提供了多个智能指针,简化了动态分配对象的管理。
Boost.Spirit Boost.Spirit 可以用类似于 EBNF (扩展巴科斯范式)的语法生成词法分析器。
Boost.StringAlgorithms Boost.StringAlgorithms 提供了多个独立的函数,以方便处理字符串。
Boost.System TR2 Boost.System 提供了一个处理系统相关或应用相关错误代码的框架。
Boost.Thread C++0x Boost.Thread 可用于开发多线程应用。
Boost.Tokenizer Boost.Tokenizer 可以对一个字符串的各个组件进行迭代。
Boost.Tuple TR1 Boost.Tuple 提供了泛化版的 std::pair,可以将任意数量的数据组在一起。
Boost.Unordered TR1 Boost.Unordered 扩展了 C++ 标准的容器,增加了boost::unordered_set 和 boost::unordered_map.
Boost.Variant Boost.Variant 可以定义多个数据类型,类似于 union, 将多个数据类型组在一起。 Boost.Variant 比 union 优胜的地方在于它可以使用类。

  Technical Report 1 是在2003年发布的,有关 C++0x 标准和 Technical Report 2 的一些细节才能反映当前的状态。由于无论是下一个版本的 C++ 标准,还是 Technical Report 2 都尚未被批准,所以在往后的时间里,它们仍然可能会有改变。

第 2 章 智能指针

2.1. 概述

  1998年修订的第一版C++标准只提供了一种智能指针: std::auto_ptr 。 它基本上就像是个普通的指针: 通过地址来访问一个动态分配的对象。 std::auto_ptr 之所以被看作是智能指针,是因为它会在析构的时候调用 delete 操作符来自动释放所包含的对象。 当然这要求在初始化的时候,传给它一个由 new 操作符返回的对象的地址。 既然 std::auto_ptr 的析构函数会调用 delete 操作符,它所包含的对象的内存会确保释放掉。 这是智能指针的一个优点。

  当和异常联系起来时这就更加重要了:没有 std::auto_ptr 这样的智能指针,每一个动态分配内存的函数都需要捕捉所有可能的异常,以确保在异常传递给函数的调用者之前将内存释放掉。 Boost C++ 库 Smart Pointers 提供了许多可以用在各种场合的智能指针。

2.2. RAII

  智能指针的原理基于一个常见的习语叫做 RAII :资源申请即初始化。 智能指针只是这个习语的其中一例——当然是相当重要的一例。 智能指针确保在任何情况下,动态分配的内存都能得到正确释放,从而将开发人员从这项任务中解放了出来。 这包括程序因为异常而中断,原本用于释放内存的代码被跳过的场景。 用一个动态分配的对象的地址来初始化智能指针,在析构的时候释放内存,就确保了这一点。 因为析构函数总是会被执行的,这样所包含的内存也将总是会被释放。

  无论何时,一定得有第二条指令来释放之前另一条指令所分配的资源时,RAII 都是适用的。 许多的 C++ 应用程序都需要动态管理内存,因而智能指针是一种很重要的 RAII 类型。 不过 RAII 本身是适用于许多其它场景的。

#include <windows.h> 

class windows_handle 
{
    
     
  public: 
    windows_handle(HANDLE h) 
      : handle_(h) 
    {
    
     
    } 

    ~windows_handle() 
    {
    
     
      CloseHandle(handle_); 
    } 

    HANDLE handle() const 
    {
    
     
      return handle_; 
    } 

  private: 
    HANDLE handle_; 
}; 

int main() 
{
    
     
  windows_handle h(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId())); 
  SetPriorityClass(h.handle(), HIGH_PRIORITY_CLASS); 
} 

  上面的例子中定义了一个名为 windows_handle 的类,它的析构函数调用了 CloseHandle() 函数。 这是一个 Windows API 函数,因而这个程序只能在 Windows 上运行。 在 Windows 上,许多资源在使用之前都要求打开。 这暗示着一旦资源不再使用之后就应该关闭。 windows_handle 类的机制能确保这一点。

  windows_handle 类的实例以一个句柄来初始化。 Windows 使用句柄来唯一的标识资源。 比如说,OpenProcess() 函数返回一个 HANDLE 类型的句柄,通过该句柄可以访问当前系统中的进程。 在示例代码中,访问的是进程自己——换句话说就是应用程序本身。

  我们通过这个返回的句柄提升了进程的优先级,这样它就能从调度器那里获得更多的 CPU 时间。 这里只是用于演示目的,并没什么实际的效应。 重要的一点是:通过 OpenProcess() 打开的资源不需要显示的调用 CloseHandle() 来关闭。 当然,应用程序终止时资源也会随之关闭。 然而,在更加复杂的应用程序里,windows_handle 类确保当一个资源不再使用时就能正确的关闭。 某个资源一旦离开了它的作用域——上例中 h 的作用域在 main() 函数的末尾——它的析构函数会被自动的调用,相应的资源也就释放掉了。

2.3. 作用域指针

  一个作用域指针独占一个动态分配的对象。 对应的类名为 boost::scoped_ptr,它的定义在 boost/scoped_ptr.hpp 中。 不像 std::auto_ptr 一个作用域指针不能传递它所包含的对象的所有权到另一个作用域指针。 一旦用一个地址来初始化,这个动态分配的对象将在析构阶段释放。

  因为一个作用域指针只是简单保存和独占一个内存地址,所以boost::scoped_ptr 的实现就要比 std::auto_ptr 简单。 在不需要所有权传递的时候应该优先使用 boost::scoped_ptr 。 在这些情况下,比起 std::auto_ptr 它是一个更好的选择,因为可以避免不经意间的所有权传递。

#include <boost/scoped_ptr.hpp> 

int main() 
{
    
     
  boost::scoped_ptr<int> i(new int); 
  *i = 1; 
  *i.get() = 2; 
  i.reset(new int); 
} 

  一经初始化,智能指针 boost::scoped_ptr 所包含的对象,可以通过类似于普通指针的接口来访问。 这是因为重载了相关的操作符 operator*(),operator->() 和 operator bool() 。 此外,还有 get() 和 reset() 方法。 前者返回所含对象的地址,后者用一个新的对象来重新初始化智能指针。 在这种情况下,新创建的对象赋值之前会先自动释放所包含的对象。

  boost::scoped_ptr 的析构函数中使用 delete 操作符来释放所包含的对象。 这对 boost::scoped_ptr 所包含的类型加上了一条重要的限制。 boost::scoped_ptr 不能用动态分配的数组来做初始化,因为这需要调用 delete[] 来释放。 在这种情况下,可以使用下面将要介绍的 boost:scoped_array 类。

2.4. 作用域数组

  作用域数组的使用方式与作用域指针相似。 关键不同在于,作用域数组的析构函数使用 delete[] 操作符来释放所包含的对象。 因为该操作符只能用于数组对象,所以作用域数组必须通过动态分配的数组来初始化。

  对应的作用域数组类名为 boost::scoped_array,它的定义在 boost/scoped_array.hpp 里。

#include <boost/scoped_array.hpp> 

int main() 
{
    
     
  boost::scoped_array<int> i(new int[2]); 
  *i.get() = 1; 
  i[1] = 2; 
  i.reset(new int[3]); 
} 

  boost:scoped_array 类重载了操作符 operator[]() 和 operator bool()。 可以通过 operator[]() 操作符访问数组中特定的元素,于是 boost::scoped_array 类型对象的行为就酷似它所含的数组。

  正如 boost::scoped_ptr 那样, boost:scoped_array 也提供了 get() 和 reset() 方法,用来返回和重新初始化所含对象的地址。

2.5. 共享指针

  这是使用率最高的智能指针,但是 C++ 标准的第一版中缺少这种指针。 它已经作为技术报告1(TR 1)的一部分被添加到标准里了。 如果开发环境支持的话,可以使用 memory 中定义的 std::shared_ptr。 在 Boost C++ 库里,这个智能指针命名为 boost::shared_ptr,定义在 boost/shared_ptr.hpp 里。

  智能指针 boost::shared_ptr 基本上类似于 boost::scoped_ptr。 关键不同之处在于 boost::shared_ptr 不一定要独占一个对象。 它可以和其他 boost::shared_ptr 类型的智能指针共享所有权。 在这种情况下,当引用对象的最后一个智能指针销毁后,对象才会被释放。

  因为所有权可以在 boost::shared_ptr 之间共享,任何一个共享指针都可以被复制,这跟 boost::scoped_ptr 是不同的。 这样就可以在标准容器里存储智能指针了——你不能在标准容器中存储 std::auto_ptr,因为它们在拷贝的时候传递了所有权。

#include <boost/shared_ptr.hpp> 
#include <vector> 

int main() 
{
    
    
	std::vector<boost::shared_ptr<int> > v; 
	v.push_back(boost::shared_ptr<int>(new int(1))); 
	v.push_back(boost::shared_ptr<int>(new int(2))); 
} 

  多亏了有 boost::shared_ptr,我们才能像上例中展示的那样,在标准容器中安全的使用动态分配的对象。 因为 boost::shared_ptr 能够共享它所含对象的所有权,所以保存在容器中的拷贝(包括容器在需要时额外创建的拷贝)都是和原件相同的。如前所述,std::auto_ptr做不到这一点,所以绝对不应该在容器中保存它们。

  类似于 boost::scoped_ptr, boost::shared_ptr 类重载了以下这些操作符:operator*(),operator->() 和 operator bool()。另外还有 get() 和 reset() 函数来获取和重新初始化所包含的对象的地址。

#include <boost/shared_ptr.hpp> 

int main() 
{
    
     
	  boost::shared_ptr<int> i1(new int(1)); 
	  boost::shared_ptr<int> i2(i1); 
	  i1.reset(new int(2)); 
} 

  本例中定义了2个共享指针 i1 和 i2,它们都引用到同一个 int 类型的对象。i1 通过 new 操作符返回的地址显示的初始化,i2 通过 i1 拷贝构造而来。 i1 接着调用 reset(),它所包含的整数的地址被重新初始化。不过它之前所包含的对象并没有被释放,因为 i2 仍然引用着它。 智能指针 boost::shared_ptr 记录了有多少个共享指针在引用同一个对象,只有在最后一个共享指针销毁时才会释放这个对象。

  默认情况下,boost::shared_ptr 使用 delete 操作符来销毁所含的对象。 然而,具体通过什么方法来销毁,是可以指定的,就像下面的例子里所展示的:

#include <boost/shared_ptr.hpp> 
#include <windows.h> 

int main() 
{
    
     
	  boost::shared_ptr<void> h(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId()), CloseHandle); 
	  SetPriorityClass(h.get(), HIGH_PRIORITY_CLASS); 
} 

  boost::shared_ptr 的构造函数的第二个参数是一个普通函数或者函数对象,该参数用来销毁所含的对象。 在本例中,这个参数是 Windows API 函数 CloseHandle()。 当变量 h 超出它的作用域时,调用的是这个函数而不是 delete 操作符来销毁所含的对象。 为了避免编译错误,该函数只能带一个 HANDLE 类型的参数, CloseHandle() 正好符合要求。

  该例和本章稍早讲述 RAII 习语时所用的例子的运行是一样的。 然而,本例没有单独定义一个 windows_handle 类,而是利用了 boost::shared_ptr 的特性,给它的构造函数传递一个方法,这个方法会在共享指针超出它的作用域时自动调用。

2.6. 共享数组

  共享数组的行为类似于共享指针。 关键不同在于共享数组在析构时,默认使用 delete[] 操作符来释放所含的对象。 因为这个操作符只能用于数组对象,共享数组必须通过动态分配的数组的地址来初始化。

  共享数组对应的类型是 boost::shared_array,它的定义在 boost/shared_array.hpp 里。

#include <boost/shared_array.hpp> 
#include <iostream> 

int main() 
{
    
     
	  boost::shared_array<int> i1(new int[2]); 
	  boost::shared_array<int> i2(i1); 
	  i1[0] = 1; 
	  std::cout << i2[0] << std::endl; 
} 

  就像共享指针那样,所含对象的所有权可以跟其他共享数组来共享。 这个例子中定义了2个变量 i1 和 i2,它们引用到同一个动态分配的数组。i1 通过 operator 操作符保存了一个整数1——这个整数可以被 i2 引用,比如打印到标准输出。

  和本章中所有的智能指针一样,boost::shared_array 也同样提供了 get() 和 reset() 方法。 另外还重载了 operator bool()。

2.7. 弱指针

  到目前为止介绍的各种智能指针都能在不同的场合下独立使用。 相反,弱指针只有在配合共享指针一起使用时才有意义。 弱指针 boost::weak_ptr 的定义在 boost/weak_ptr.hpp里。

#include <windows.h> 
#include <boost/shared_ptr.hpp> 
#include <boost/weak_ptr.hpp> 
#include <iostream> 

DWORD WINAPI reset(LPVOID p) 
{
    
     
	  boost::shared_ptr<int> *sh = static_cast<boost::shared_ptr<int>*>(p); 
	  sh->reset(); 
	  return 0; 
} 

DWORD WINAPI print(LPVOID p) 
{
    
     
	  boost::weak_ptr<int> *w = static_cast<boost::weak_ptr<int>*>(p); 
	  boost::shared_ptr<int> sh = w->lock(); 
	  if (sh) 
	    std::cout << *sh << std::endl; 
	  return 0; 
} 

int main() 
{
    
     
	  boost::shared_ptr<int> sh(new int(99)); 
	  boost::weak_ptr<int> w(sh); 
	  HANDLE threads[2]; 
	  threads[0] = CreateThread(0, 0, reset, &sh, 0, 0); 
	  threads[1] = CreateThread(0, 0, print, &w, 0, 0); 
	  WaitForMultipleObjects(2, threads, TRUE, INFINITE); 
} 

  boost::weak_ptr 必定总是通过 boost::shared_ptr 来初始化的。一旦初始化之后,它基本上只提供一个有用的方法: lock()。此方法返回的boost::shared_ptr 与用来初始化弱指针的共享指针共享所有权。 如果这个共享指针不含有任何对象,返回的共享指针也将是空的。

  当函数需要一个由共享指针所管理的对象,而这个对象的生存期又不依赖于这个函数时,就可以使用弱指针。 只要程序中还有一个共享指针掌管着这个对象,函数就可以使用该对象。 如果共享指针复位了,就算函数里能得到一个共享指针,对象也不存在了。

  上例的 main() 函数中,通过 Windows API 创建了2个线程。 于是乎,该例只能在 Windows 平台上编译运行。

  第一个线程函数 reset() 的参数是一个共享指针的地址。 第二个线程函数 print() 的参数是一个弱指针的地址。 这个弱指针是之前通过共享指针初始化的。

  一旦程序启动之后,reset() 和 print() 就都开始执行了。 不过执行顺序是不确定的。 这就导致了一个潜在的问题:reset() 线程在销毁对象的时候print() 线程可能正在访问它。

  通过调用弱指针的 lock() 函数可以解决这个问题:如果对象存在,那么 lock() 函数返回的共享指针指向这个合法的对象。否则,返回的共享指针被设置为0,这等价于标准的null指针。

  弱指针本身对于对象的生存期没有任何影响。 lock() 返回一个共享指针,print() 函数就可以安全的访问对象了。 这就保证了——即使另一个线程要释放对象——由于我们有返回的共享指针,对象依然存在。

2.8. 介入式指针

  大体上,介入式指针的工作方式和共享指针完全一样。 boost::shared_ptr 在内部记录着引用到某个对象的共享指针的数量,可是对介入式指针来说,程序员就得自己来做记录。 对于框架对象来说这就特别有用,因为它们记录着自身被引用的次数。

  介入式指针 boost::intrusive_ptr 定义在 boost/intrusive_ptr.hpp 里。

#include <boost/intrusive_ptr.hpp> 
#include <atlbase.h> 
#include <iostream> 

void intrusive_ptr_add_ref(IDispatch *p) 
{
    
     
  	p->AddRef(); 
} 

void intrusive_ptr_release(IDispatch *p) 
{
    
     
  	p->Release(); 
} 

void check_windows_folder() 
{
    
     
	  CLSID clsid; 
	  CLSIDFromProgID(CComBSTR("Scripting.FileSystemObject"), &clsid); 
	  void *p; 
	  CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER, __uuidof(IDispatch), &p); 
	  boost::intrusive_ptr<IDispatch> disp(static_cast<IDispatch*>(p)); 
	  CComDispatchDriver dd(disp.get()); 
	  CComVariant arg("C:\\Windows"); 
	  CComVariant ret(false); 
	  dd.Invoke1(CComBSTR("FolderExists"), &arg, &ret); 
	  std::cout << (ret.boolVal != 0) << std::endl; 
} 

void main() 
{
    
     
	  CoInitialize(0); 
	  check_windows_folder(); 
	  CoUninitialize(); 
} 

  上面的例子中使用了 COM(组件对象模型)提供的函数,于是乎只能在 Windows 平台上编译运行。 COM 对象是使用 boost::intrusive_ptr 的绝佳范例,因为 COM 对象需要记录当前有多少指针引用着它。 通过调用 AddRef() 和 Release() 函数,内部的引用计数分别增 1 或者减 1。当引用计数为 0 时,COM 对象自动销毁。

  在 intrusive_ptr_add_ref() 和 intrusive_ptr_release() 内部调用 AddRef() 和 Release() 这两个函数,来增加或减少相应 COM 对象的引用计数。 这个例子中用到的 COM 对象名为 ‘FileSystemObject’,在 Windows 上它是默认可用的。通过这个对象可以访问底层的文件系统,比如检查一个给定的目录是否存在。 在上例中,我们检查 C:\Windows 目录是否存在。 具体它在内部是怎么实现的,跟 boost::intrusive_ptr 的功能无关,完全取决于 COM。 关键点在于一旦介入式指针 disp 离开了它的作用域——check_windows_folder() 函数的末尾,函数 intrusive_ptr_release() 将会被自动调用。 这将减少 COM 对象 ‘FileSystemObject’ 的内部引用计数到0,于是该对象就销毁了。

2.9. 指针容器

  在你见过 Boost C++ 库的各种智能指针之后,应该能够编写安全的代码,来使用动态分配的对象和数组。多数时候,这些对象要存储在容器里——如上所述——使用 boost::shared_ptr 和 boost::shared_array 这就相当简单了。

#include <boost/shared_ptr.hpp> 
#include <vector> 

int main() 
{
    
     
  std::vector<boost::shared_ptr<int> > v; 
  v.push_back(boost::shared_ptr<int>(new int(1))); 
  v.push_back(boost::shared_ptr<int>(new int(2))); 
} 

  上面例子中的代码当然是正确的,智能指针确实可以这样用,然而因为某些原因,实际情况中并不这么用。 第一,反复声明 boost::shared_ptr 需要更多的输入。 其次,将 boost::shared_ptr 拷进,拷出,或者在容器内部做拷贝,需要频繁的增加或者减少内部引用计数,这肯定效率不高。 由于这些原因,Boost C++ 库提供了 指针容器 专门用来管理动态分配的对象。

#include <boost/ptr_container/ptr_vector.hpp> 

int main() 
{
    
     
  boost::ptr_vector<int> v; 
  v.push_back(new int(1)); 
  v.push_back(new int(2)); 
} 

  boost::ptr_vector 类的定义在 boost/ptr_container/ptr_vector.hpp 里,它跟前一个例子中用 boost::shared_ptr 模板参数来初始化的容器具有相同的工作方式。 boost::ptr_vector 专门用于动态分配的对象,它使用起来更容易也更高效。 boost::ptr_vector 独占它所包含的对象,因而容器之外的共享指针不能共享所有权,这跟 std::vector<boost::shared_ptr > 相反。

  除了 boost::ptr_vector 之外,专门用于管理动态分配对象的容器还包括:boost::ptr_deque, boost::ptr_list, boost::ptr_set, boost::ptr_map, boost::ptr_unordered_set 和 boost::ptr_unordered_map。这些容器等价于C++标准里提供的那些。最后两个容器对应于std::unordered_set 和 std::unordered_map,它们作为技术报告1的一部分加入 C++ 标准。 如果所使用的 C++ 标准实现不支持技术报告1的话,还可以使用 Boost C++ 库里实现的 boost::unordered_set 和 boost::unordered_map。

第 3 章 函数对象

3.1. 概述

  本章介绍的是函数对象,可能称为 高阶函数’更为适合。 它实际上是指那些可以被传入到其它函数或是从其它函数返回的一类函数。 在C++中高阶函数是被实现为函数对象的,所以这个标题还是有意义的。

  在这整一章中,将会介绍几个用于处理函数对象的 Boost C++ 库。 其中,Boost.Bind 可替换来自C++标准的著名的 std::bind1st() 和 std::bind2nd() 函数,而 Boost.Function 则提供了一个用于封装函数指针的类。 最后,Boost.Lambda 则引入了一种创建匿名函数的方法。

3.2. Boost.Bind

  Boost.Bind 是这样的一个库,它简化了由C++标准中的 std::bind1st() 和 std::bind2nd() 模板函数所提供的一个机制:将这些函数与几乎不限数量的参数一起使用,就可以得到指定签名的函数。 这种情形的一个最好的例子就是在C++标准中定义的多个不同算法。

#include <iostream> 
#include <vector> 
#include <algorithm> 

void print(int i) 
{
    
     
  	std::cout << i << std::endl; 
} 

int main() 
{
    
    
	  std::vector<int> v; 
	  v.push_back(1); 
	  v.push_back(3); 
	  v.push_back(2); 

	  std::for_each(v.begin(), v.end(), print); 
} 

  算法 std::for_each() 要求它的第三个参数是一个仅接受正好一个参数的函数或函数对象。 如果 std::for_each() 被执行,指定容器中的所有元素 - 在上例中,这些元素的类型为 int - 将按顺序被传入至 print() 函数。 但是,如果要使用一个具有不同签名的函数的话,事情就复杂了。 例如,如果要传入的是以下函数 add(),它要将一个常数值加至容器中的每个元素上,并显示结果。

void add(int i, int j) 
{
    
     
 	 std::cout << i + j << std::endl; 
} 

  由于 std::for_each() 要求的是仅接受一个参数的函数,所以不能直接传入 add() 函数。 源代码必须要修改。

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

class add 
  : public std::binary_function<int, int, void> 
{
    
     
public: 
	  void operator()(int i, int j) const 
	  {
    
     
	    std::cout << i + j << std::endl; 
	  } 
}; 

int main() 
{
    
     
	  std::vector<int> v; 
	  v.push_back(1); 
	  v.push_back(3); 
	  v.push_back(2); 
	
	  std::for_each(v.begin(), v.end(), std::bind1st(add(), 10)); 
} 

  以上程序将值10加至容器 v 的每个元素之上,并使用标准输出流显示结果。 源代码必须作出大幅的修改,以实现此功能:add() 函数已被转换为一个派生自 std::binary_function 的函数对象。

  Boost.Bind 简化了不同函数之间的绑定。 它只包含一个 boost::bind() 模板函数,定义于 boost/bind.hpp 中。 使用这个函数,可以如下实现以上例子:

#include <boost/bind.hpp> 
#include <iostream> 
#include <vector> 
#include <algorithm> 

void add(int i, int j) 
{
    
     
	  std::cout << i + j << std::endl; 
} 

int main() 
{
    
     
	  std::vector<int> v; 
	  v.push_back(1); 
	  v.push_back(3); 
	  v.push_back(2); 
	
	  std::for_each(v.begin(), v.end(), boost::bind(add, 10, _1)); 
} 

  象 add() 这样的函数不再需要为了要用于 std::for_each() 而转换为函数对象。 使用 boost::bind(),这个函数可以忽略其第一个参数而使用。

  因为 add() 函数要求两个参数,两个参数都必须传递给 boost::bind()。 第一个参数是常数值10,而第二个参数则是一个怪异的 _1。

  _1 被称为占位符(placeholder),定义于 Boost.Bind。 除了 _1,Boost.Bind 还定义了 _2 和 _3。 通过使用这些占位符,boost::bind() 可以变为一元、二元或三元的函数。 对于 _1, boost::bind() 变成了一个一元函数 —— 即只要求一个参数的函数。 这是必需的,因为 std::for_each() 正是要求一个一元函数作为其第三个参数。

  当这个程序执行时,std::for_each() 对容器 v 中的第一个元素调用该一元函数。 元素的值通过占位符 _1 传入到一元函数中。 这个占位符和常数值被进一步传递到 add() 函数。 通过使用这种机制,std::for_each() 只看到了由 boost::bind() 所定义的一元函数。 而 boost::bind() 本身则只是调用了另一个函数,并将常数值或占位符作为参数传入给它。

  下面这个例子通过 boost::bind() 定义了一个二元函数,用于 std::sort() 算法,该算法要求一个二元函数作为其第三个参数。

#include <boost/bind.hpp> 
#include <vector> 
#include <algorithm> 

bool compare(int i, int j) 
{
    
     
  return i > j; 
} 

int main() 
{
    
     
  std::vector<int> v; 
  v.push_back(1); 
  v.push_back(3); 
  v.push_back(2); 

  std::sort(v.begin(), v.end(), boost::bind(compare, _1, _2)); 
} 

  因为使用了两个占位符 _1 和 _2,所以 boost::bind() 定义了一个二元函数。 std::sort() 算法以容器 v 的两个元素来调用该函数,并根据返回值来对容器进行排序。 基于 compare() 函数的定义,容器将被按降序排列。

  但是,由于 compare() 本身就是一个二元函数,所以使用 boost::bind() 确是多余的。

#include <boost/bind.hpp> 
#include <vector> 
#include <algorithm> 

bool compare(int i, int j) 
{
    
     
  return i > j; 
} 

int main() 
{
    
     
  std::vector<int> v; 
  v.push_back(1); 
  v.push_back(3); 
  v.push_back(2); 

  std::sort(v.begin(), v.end(), compare); 
} 

  不过使用 boost::bind() 还是有意义的。例如,如果容器要按升序排列而又不能修改 compare() 函数的定义。

#include <boost/bind.hpp> 
#include <vector> 
#include <algorithm> 

bool compare(int i, int j) 
{
    
     
  return i > j; 
} 

int main() 
{
    
     
  std::vector<int> v; 
  v.push_back(1); 
  v.push_back(3); 
  v.push_back(2); 

  std::sort(v.begin(), v.end(), boost::bind(compare, _2, _1)); 
} 

  该例子仅改变了占位符的顺序:_2 被作为第一参数传递,而 _1 则被作为第二参数传递至 compare(),这样即可改变排序的顺序。

3.3. Boost.Ref

  本库 Boost.Ref 通常与 Boost.Bind 一起使用,所以我把它们挨着写。 它提供了两个函数 - boost::ref() 和 boost::cref() - 定义于 boost/ref.hpp

  当要用于 boost::bind() 的函数带有至少一个引用参数时,Boost.Ref 就很重要了。 由于 boost::bind() 会复制它的参数,所以引用必须特别处理。

#include <boost/bind.hpp> 
#include <iostream> 
#include <vector> 
#include <algorithm> 

void add(int i, int j, std::ostream &os) 
{
    
     
 	 os << i + j << std::endl; 
} 

int main() 
{
    
     
	  std::vector<int> v; 
	  v.push_back(1); 
	  v.push_back(3); 
	  v.push_back(2); 
	
	  std::for_each(v.begin(), v.end(), boost::bind(add, 10, _1, boost::ref(std::cout))); 
} 

  以上例子使用了上一节中的 add() 函数。 不过这一次该函数需要一个流对象的引用来打印信息。 因为传给 boost::bind() 的参数是以值方式传递的,所以 std::cout 不能直接使用,否则该函数会试图创建它的一份拷贝。

  通过使用模板函数 boost::ref(),象 std::cout 这样的流就可以被以引用方式传递,也就可以成功编译上面这个例子了。

  要以引用方式传递常量对象,可以使用模板函数 boost::cref()。

3.4. Boost.Function

  为了封装函数指针,Boost.Function 提供了一个名为 boost::function 的类。 它定义于 boost/function.hpp ,用法如下:

#include <boost/function.hpp> 
#include <iostream> 
#include <cstdlib> 
#include <cstring> 

int main() 
{
    
     
	  boost::function<int (const char*)> f = std::atoi; 
	  std::cout << f("1609") << std::endl; 
	  f = std::strlen; 
	  std::cout << f("1609") << std::endl; 
} 

  boost::function 可以定义一个指针,指向具有特定签名的函数。 以上例子定义了一个指针 f,它可以指向某个接受一个类型为 const char* 的参数且返回一个类型为 int 的值的函数。 定义完成后,匹配此签名的函数均可赋值给这个指针。 这个例程就是先将 std::atoi() 赋值给 f,然后再将它重赋值为 std::strlen()。

  注意,给定的数据类型并不需要精确匹配:虽然 std::strlen() 是以 std::size_t 作为返回类型的,但是它也可以被赋值给 f。

  因为 f 是一个函数指针,所以被赋值的函数可以通过重载的 operator()() 操作符来调用。 取决于当前被赋值的是哪一个函数,在以上例子中将调用 std::atoi() 或 std::strlen()。

  如果 f 未赋予一个函数而被调用,则会抛出一个 boost::bad_function_call 异常。

#include <boost/function.hpp> 
#include <iostream> 

int main() 
{
    
     
	  try 
	  {
    
     
	    boost::function<int (const char*)> f; 
	    f(""); 
	  } 
	  catch (boost::bad_function_call &ex) 
	  {
    
     
	    std::cout << ex.what() << std::endl; 
	  } 
} 

  注意,将值 0 赋给一个 boost::function 类型的函数指针,将会释放当前所赋的函数。 释放之后再调用它也会导致 boost::bad_function_call 异常被抛出。 要检查一个函数指针是否被赋值某个函数,可以使用 empty() 函数或 operator bool() 操作符。

  通过使用 Boost.Function,类成员函数也可以被赋值给类型为 boost::function 的对象。

#include <boost/function.hpp> 
#include <iostream> 

struct world 
{
    
     
	  void hello(std::ostream &os) 
	  {
    
     
	    os << "Hello, world!" << std::endl; 
	  } 
}; 

int main() 
{
    
     
	  boost::function<void (world*, std::ostream&)> f = &world::hello; 
	  world w; 
	  f(&w, boost::ref(std::cout)); 
} 

  在调用这样的一个函数时,传入的第一个参数表示了该函数被调用的那个特定对象。 因此,在模板定义中的左括号后的第一个参数必须是该特定类的指针。 接下来的参数才是表示相应的成员函数的签名。

  这个程序还使用了来自 Boost.Ref 库的 boost::ref(),它提供了一个方便的机制向 Boost.Function 传递引用。

3.5. Boost.Lambda

  匿名函数 - 又称为 lambda 函数 - 已经在多种编程语言中存在,但 C++ 除外。 不过在 Boost.Lambda 库的帮助下,现在在 C++ 应用中也可以使用它们了。

  lambda 函数的目标是令源代码更为紧凑,从而也更容易理解。 以本章第一节中的代码例子为例。

#include <iostream> 
#include <vector> 
#include <algorithm> 

void print(int i) 
{
    
     
  	std::cout << i << std::endl; 
} 

int main() 
{
    
     
	  std::vector<int> v; 
	  v.push_back(1); 
	  v.push_back(3); 
	  v.push_back(2); 
	
	  std::for_each(v.begin(), v.end(), print); 
} 

  这段程序接受容器 v 中的元素并使用 print() 函数将它们写出到标准输出流。 由于 print() 只是写出一个简单的 int,所以该函数的实现相当简单。 严格来说,它是如此地简单,以致于如果可以在 std::for_each() 算法里面直接定义它的话,会更为方便; 从而省去增加一个函数的需要。 另外一个好处是代码更为紧凑,使得算法与负责数据输出的函数不是局部性分离的。 Boost.Lambda 正好使之成为现实。

#include <boost/lambda/lambda.hpp> 
#include <iostream> 
#include <vector> 
#include <algorithm> 

int main() 
{
    
     
	  std::vector<int> v; 
	  v.push_back(1); 
	  v.push_back(3); 
	  v.push_back(2); 
	
	  std::for_each(v.begin(), v.end(), std::cout << boost::lambda::_1 << "\n"); 
} 

  Boost.Lambda 提供了几个结构来定义匿名函数。 代码就被置于执行的地方,从而省去将它包装为一个函数再进行相应的函数调用的这些开销。 与原来的例子一样,这个程序将容器 v 的所有元素写出至标准输出流。

  与 Boost.Bind 相类似,Boost.Lambda 也定义了三个占位符,名为 _1, _2 和 _3。 但与 Boost.Bind 不同的是,这些占位符是定义在单独的名字空间的。 因此,该例中的第一个占位符是通过 boost::lambda::_1 来引用的。 为了满足编译器的要求,必须包含相应的头文件 boost/lambda/lambda.hpp

  虽然代码的位置位于 std::for_each() 的第三个参数处,看起来很怪异,但 Boost.Lambda 可以写出正常的 C++ 代码。 通过使用占位符,容器 v 的元素可以通过 << 传给 std::cout 以将它们写出到标准输出流。

  虽然 Boost.Lambda 非常强大,但也有一些缺点。 要在以上例子中插入换行的话,必须用 “\n” 来替代 std::endl 才能成功编译。 因为一元 std::endl 模板函数所要求的类型不同于 lambda 函数 std::cout << boost::lambda::_1 的函数,所以在此不能使用它。

  下一个版本的 C++ 标准很可能会将 lambda 函数作为 C++ 语言本身的组成部分加入,从而消除对单独的库的需要。 但是在下一个版本到来并被不同的编译器厂商所采用可能还需要好几年。 在此之前,Boost.Lambda 被证明是一个完美的替代品,从以下例子可以看出,这个例子只将大于1的元素写出到标准输出流。

#include <boost/lambda/lambda.hpp> 
#include <boost/lambda/if.hpp> 
#include <iostream> 
#include <vector> 
#include <algorithm> 

int main() 
{
    
     
	  std::vector<int> v; 
	  v.push_back(1); 
	  v.push_back(3); 
	  v.push_back(2); 
	
	  std::for_each(v.begin(), v.end(), 
	  boost::lambda::if_then(boost::lambda::_1 > 1, 
	  std::cout << boost::lambda::_1 << "\n")); 
} 

  头文件 boost/lambda/if.hpp 定义了几个结构,允许在 lambda 函数内部使用 if 语句。 最基本的结构是 boost::lambda::if_then() 模板函数,它要求两个参数:第一个参数对条件求值 —— 如果为真,则执行第二个参数。 如例中所示,每个参数本身都可以是 lambda 函数。

  除了 boost::lambda::if_then(), Boost.Lambda 还提供了 boost::lambda::if_then_else() 和 boost::lambda::if_then_else_return() 模板函数 - 它们都要求三个参数。 另外还提供了用于实现循环、转型操作符,甚至是 throw - 允许 lambda 函数抛出异常 —— 的模板函数。

  虽然可以用这些模板函数在 C++ 中构造出复杂的 lambda 函数,但是你必须要考虑其它方面,如可读性和可维护性。 因为别人需要学习并理解额外的函数,如用 boost::lambda::if_then() 来替代已知的 C++ 关键字 if 和 else,lambda 函数的好处通常随着它的复杂性而降低。 多数情况下,更为合理的方法是用熟悉的 C++ 结构定义一个单独的函数。

第 4 章 事件处理

4.1. 概述

  很多开发者在听到术语 事件处理 时就会想到GUI:点击一下某个按钮,相关联的功能就会被执行。 点击本身就是事件,而功能就是相对应的事件处理器。

  这一模式的使用当然不仅限于GUI。 一般情况下,任意对象都可以调用基于特定事件的专门函数。 本章所介绍的 Boost.Signals 库提供了一个简单的方法在 C++ 中应用这一模式。

  严格来说,Boost.Function 库也可以用于事件处理。 不过,Boost.Function 和 Boost.Signals 之间的一个主要区别在于,Boost.Signals 能够将一个以上的事件处理器关联至单个事件。 因此,Boost.Signals 可以更好地支持事件驱动的开发,当需要进行事件处理时,应作为第一选择。

4.2. 信号 Signals

  虽然这个库的名字乍一看好象有点误导,但实际上并非如此。 Boost.Signals 所实现的模式被命名为 ‘信号至插槽’ (signal to slot),它基于以下概念:当对应的信号被发出时,相关联的插槽即被执行。 原则上,你可以把单词 ‘信号’ 和 ‘插槽’ 分别替换为 ‘事件’ 和 ‘事件处理器’。 不过,由于信号可以在任意给定的时间发出,所以这一概念放弃了 ‘事件’ 的名字。

  因此,Boost.Signals 没有提供任何类似于 ‘事件’ 的类。 相反,它提供了一个名为 boost::signal 的类,定义于 boost/signal.hpp 。实际上,这个头文件是唯一 一个需要知道的,因为它会自动包含其它相关的头文件。

  Boost.Signals 定义了其它一些类,位于 boost::signals 名字空间中。 由于 boost::signal 是最常被用到的类,所以它是位于名字空间 boost 中的。

#include <boost/signal.hpp> 
#include <iostream> 

void func() 
{
    
     
  	std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{
    
     
	  boost::signal<void ()> s; 
	  s.connect(func); 
	  s(); 
} 

  boost::signal 实际上被实现为一个模板函数,具有被用作为事件处理器的函数的签名,该签名也是它的模板参数。 在这个例子中,只有签名为 void () 的函数可以被成功关联至信号 s。

  函数 func() 被通过 connect() 方法关联至信号 s。 由于 func() 符合所要求的 void () 签名,所以该关联成功建立。因此当信号 s 被触发时,func() 将被调用。

  信号是通过调用 s 来触发的,就象普通的函数调用那样。 这个函数的签名对应于作为模板参数传入的签名:因为 void () 不要求任何参数,所以括号内是空的。

  调用 s 会引发一个触发器,进而执行相应的 func() 函数 —— 之前用 connect() 关联了的。

  同一例子也可以用 Boost.Function 来实现。

#include <boost/function.hpp> 
#include <iostream> 

void func() 
{
    
     
  	std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{
    
     
	  boost::function<void ()> f; 
	  f = func; 
	  f(); 
} 

  和前一个例子相类似,func() 被关联至 f。 当 f 被调用时,就会相应地执行 func()。 Boost.Function 仅限于这种情形下适用,而 Boost.Signals 则提供了多得多的方式,如关联多个函数至单个特定信号,示例如下。

#include <boost/signal.hpp> 
#include <iostream> 

void func1() 
{
    
     
  	std::cout << "Hello" << std::flush; 
} 

void func2() 
{
    
     
  	std::cout << ", world!" << std::endl; 
} 

int main() 
{
    
     
	  boost::signal<void ()> s; 
	  s.connect(func1); 
	  s.connect(func2); 
	  s(); 
} 

  boost::signal 可以通过反复调用 connect() 方法来把多个函数赋值给单个特定信号。 当该信号被触发时,这些函数被按照之前用 connect() 进行关联时的顺序来执行

  另外,执行的顺序也可通过 connect() 方法的另一个重载版本来明确指定,该重载版本要求以一个 int 类型的值作为额外的参数。

#include <boost/signal.hpp> 
#include <iostream> 

void func1() 
{
    
     
  	std::cout << "Hello" << std::flush; 
} 

void func2() 
{
    
     
 	 std::cout << ", world!" << std::endl; 
} 

int main() 
{
    
     
	  boost::signal<void ()> s; 
	  s.connect(1, func2); 
	  s.connect(0, func1); 
	  s(); 
} 

  和前一个例子一样,func1() 在 func2() 之前执行。

  要释放某个函数与给定信号的关联,可以用 disconnect() 方法。

#include <boost/signal.hpp> 
#include <iostream> 

void func1() 
{
    
     
  std::cout << "Hello" << std::endl; 
} 

void func2() 
{
    
     
  std::cout << ", world!" << std::endl; 
} 

int main() 
{
    
     
  boost::signal<void ()> s; 
  s.connect(func1); 
  s.connect(func2); 
  s.disconnect(func2); 
  s(); 
} 

  这个例子仅输出 Hello,因为与 func2() 的关联在触发信号之前已经被释放。

  除了 connect() 和 disconnect() 以外,boost::signal 还提供了几个方法。

#include <boost/signal.hpp> 
#include <iostream> 

void func1() 
{
    
     
 	 std::cout << "Hello" << std::flush; 
} 

void func2() 
{
    
     
  	std::cout << ", world!" << std::endl; 
} 

int main() 
{
    
     
	  boost::signal<void ()> s; 
	  s.connect(func1); 
	  s.connect(func2); 
	  std::cout << s.num_slots() << std::endl; 
	  if (!s.empty()) 
	    s(); 
	  s.disconnect_all_slots(); 
} 

  num_slots() 返回已关联函数的数量。如果没有函数被关联,则 num_slots() 返回0。 在这种特定情况下,可以用 empty() 方法来替代。 disconnect_all_slots() 方法所做的实际上正是它的名字所表达的:释放所有已有的关联。

  看完了函数如何被关联至信号,以及弄明白了信号被触发时会发生什么事之后,还有一个问题:这些函数的返回值去了哪里? 以下例子回答了这个问题。

#include <boost/signal.hpp> 
#include <iostream> 

int func1() 
{
    
     
  	return 1; 
} 

int func2() 
{
    
     
  	return 2; 
} 

int main() 
{
    
     
	  boost::signal<int ()> s; 
	  s.connect(func1); 
	  s.connect(func2); 
	  std::cout << s() << std::endl; 
} 

  func1() 和 func2() 都具有 int 类型的返回值。 s 将处理两个返回值,并将它们都写出至标准输出流。 那么,到底会发生什么呢?

  以上例子实际上会把 2 写出至标准输出流。 两个返回值都被 s 正确接收,但除了最后一个值,其它值都会被忽略。 缺省情况下,所有被关联函数中,实际上只有最后一个返回值被返回。

  你可以定制一个信号,令每个返回值都被相应地处理。 为此,要把一个称为合成器(combiner)的东西作为第二个参数传递给 boost::signal。

#include <boost/signal.hpp> 
#include <iostream> 
#include <algorithm> 

int func1() 
{
    
     
   return 1; 
} 

int func2() 
{
    
     
   return 2; 
} 

template <typename T> 
struct min_element 
{
    
     
	  typedef T result_type; 
	
	  template <typename InputIterator> 
	  T operator()(InputIterator first, InputIterator last) const 
	  {
    
     
	    return *std::min_element(first, last); 
	  } 
}; 

int main() 
{
    
     
	  boost::signal<int (), min_element<int> > s; 
	  s.connect(func1); 
	  s.connect(func2); 
	  std::cout << s() << std::endl; 
} 

  合成器是一个重载了 operator()() 操作符的类。这个操作符会被自动调用,传入两个迭代器,指向某个特定信号的所有返回值。 以上例子使用了标准 C++ 算法 std::min_element() 来确定并返回最小的值。

  不幸的是,我们不可能把象 std::min_element() 这样的一个算法直接传给 boost::signal 作为一个模板参数。 boost::signal 要求这个合成器定义一个名为 result_type 的类型,用于说明 operator()() 操作符返回值的类型。 由于在标准 C++ 算法中缺少这个类型,所以在编译时会产生一个相应的错误。

  除了对返回值进行分析以外,合成器也可以保存它们。

#include <boost/signal.hpp> 
#include <iostream> 
#include <vector> 
#include <algorithm> 

int func1() 
{
    
     
  	return 1; 
} 

int func2() 
{
    
     
  	return 2; 
} 

template <typename T> 
struct min_element 
{
    
     
	  typedef T result_type; 
	
	  template <typename InputIterator> 
	  T operator()(InputIterator first, InputIterator last) const 
	  {
    
     
	    return T(first, last); 
	  } 
}; 

int main() 
{
    
     
	  boost::signal<int (), min_element<std::vector<int> > > s; 
	  s.connect(func1); 
	  s.connect(func2); 
	  std::vector<int> v = s(); 
	  std::cout << *std::min_element(v.begin(), v.end()) << std::endl; 
} 

  这个例子把所有返回值保存在一个 vector 中,再由 s() 返回。

4.3. 连接 Connections

  函数可以通过由 boost::signal 所提供的 connect() 和 disconnect() 方法的帮助来进行管理。 由于 connect() 会返回一个类型为 boost::signals::connection 的值,它们可以通过其它方法来管理。

#include <boost/signal.hpp> 
#include <iostream> 

void func() 
{
    
     
 	 std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{
    
     
	  boost::signal<void ()> s; 
	  boost::signals::connection c = s.connect(func); 
	  s(); 
	  c.disconnect(); 
} 

  boost::signal 的 disconnect() 方法需要传入一个函数指针,而直接调用 boost::signals::connection 对象上的 disconnect() 方法则略去该参数。

  除了 disconnect() 方法之外,boost::signals::connection 还提供了其它方法,如 block() 和 unblock()。

#include <boost/signal.hpp> 
#include <iostream> 

void func() 
{
    
     
	  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{
    
     
	  boost::signal<void ()> s; 
	  boost::signals::connection c = s.connect(func); 
	  c.block(); 
	  s(); 
	  c.unblock(); 
	  s(); 
} 

  以上程序只会执行一次 func()。 虽然信号 s 被触发了两次,但是在第一次触发时 func() 不会被调用,因为连接 c 实际上已经被 block() 调用所阻塞。 由于在第二次触发之前调用了 unblock(),所以之后 func() 被正确地执行。

  除了 boost::signals::connection 以外,还有一个名为 boost::signals::scoped_connection 的类,它会在析构时自动释放连接。

#include <boost/signal.hpp> 
#include <iostream> 

void func() 
{
    
     
  	std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{
    
     
	  boost::signal<void ()> s; 
	  {
    
     
	    boost::signals::scoped_connection c = s.connect(func); 
	  } 
	  s(); 
} 

  因为连接对象 c 在信号触发之前被销毁,所以 func() 不会被调用。

  boost::signals::scoped_connection 实际上是派生自 boost::signals::connection 的,所以它提供了相同的方法。它们之间的区别仅在于,在析构 boost::signals::scoped_connection 时,连接会自动释放。

  虽然 boost::signals::scoped_connection 的确令自动释放连接更为容易,但是该类型的对象仍需要管理。 如果在其它情形下连接也可以被自动释放,而且不需要管理这些对象的话,就更好了。

#include <boost/signal.hpp> 
#include <boost/bind.hpp> 
#include <iostream> 
#include <memory> 

class world 
{
    
     
public: 
	  void hello() const 
	  {
    
     
	    	std::cout << "Hello, world!" << std::endl; 
	  } 
}; 

int main() 
{
    
     
	  boost::signal<void ()> s; 
	  {
    
     
	    std::auto_ptr<world> w(new world()); 
	    s.connect(boost::bind(&world::hello, w.get())); 
	  } 
	  std::cout << s.num_slots() << std::endl; 
	  s(); 
} 

  以上程序使用 Boost.Bind 将一个对象的方法关联至一个信号。 在信号触发之前,这个对象就被销毁了,这会产生问题。 我们不传递实际的对象 w,而只传递一个指针给 boost::bind()。 在 s() 被实际调用的时候,该指针所引向的对象已不再存在。

  可以如下修改这个程序,使得一旦对象 w 被销毁,连接就会自动释放。

#include <boost/signal.hpp> 
#include <boost/bind.hpp> 
#include <iostream> 
#include <memory> 

class world : 
  public boost::signals::trackable 
{
    
     
public: 
    void hello() const 
    {
    
     
      	std::cout << "Hello, world!" << std::endl; 
    } 
}; 

int main() 
{
    
     
  boost::signal<void ()> s; 
  {
    
     
	  std::auto_ptr<world> w(new world()); 
	  {
    
    
	  		s.connect(boost::bind(&world::hello, w.get())); 
	  } 
	  std::cout << s.num_slots() << std::endl; 
	  s(); 
} 

  如果现在再执行,num_slots() 会返回 0 以确保不会试图调用已销毁对象之上的方法。 仅需的修改是让 world 类继承自 boost::signals::trackable。 当使用对象的指针而不是对象的副本来关联函数至信号时,boost::signals::trackable 可以显著简化连接的管理。

第 5 章 字符串处理

5.1. 前言

  在标准 C++ 中,用于处理字符串的是 std::string 类,它提供很多字符串操作,包括查找指定字符或子串的函数。 尽管 std::string 囊括了百余函数,是标准 C++ 中最为臃肿的类之一,然而却并不能满足很多开发者在日常工作中的需要。 例如, Java 和 .Net 提供了可以将字符串转换到大写字母的函数,而 std::string 就没有相应的功能。 Boost C++ 库试图弥补这一缺憾。

5.2. 区域设置

  在进入正题之前,有必要先审视下区域设置的问题,本章中提到的很多函数都需要一个附加的区域设置参数。

  区域设置在标准 C++ 中封装了文化习俗相关的内容,包括货币符号,日期时间格式, 分隔整数部分与分数部分的符号(基数符)以及多于三个数字时的分隔符(千位符)。

  在字符串处理方面,区域设置和特定文化中对字符次序以及特殊字符的描述有关。 例如,字母表中是否含有变异元音字母以及其在字母表中的位置都由语言文化决定。

  如果一个函数用于将字符串转换为大写形式,那么其实施步骤取决于具体的区域设置。

  使用类 std::string 时区域设置可以忽略, 因为它的函数均不依赖于特定语言。 然而在本章中为了使用 Boost C++ 库, 区域设置的知识是必不可少的。

  C++ 标准中在 locale 文件中定义了类 std::locale 。 每个 C++ 程序自动拥有一个此类的实例, 即不能直接访问的全局区域设置。 如果要访问它,需要使用默认构造函数构造类 std::locale 的对象,并使用与全局区域设置相同的属性初始化。

#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale loc; 
	  std::cout << loc.name() << std::endl; 
} 

  以上程序在标准输出流输出 C ,这是基本区域设置的名称,它包括了 C 语言编写的程序中默认使用的描述。

  这也是每个 C++ 应用的默认全局区域设置,它包括了美式文化中使用的描述。 例如,货币符号使用美元符号,基字符为英文句号,日期中的月份用英语书写。

  全局区域设置可以使用类 std::locale 中的静态函数 global() 改变。

#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::locale loc; 
	  std::cout << loc.name() << std::endl; 
} 

  静态函数 global() 接受一个类型为 std::locale 的对象作为其唯一的参数。 此类的另一个版本的构造函数接受类型为 const char* 的字符串,可以为一个特别的文化创建区域设置对象。 然而,除了 C 区域设置相应地命名为 “C” 之外,其他区域设置的名字并没有标准化,所以这依赖于接受区域设置名字的 C++ 标准库。 在使用 Visual Studio 2008 的情况下,语言字符串文档 指出, 可以使用语言字符串 “German” 。

5.3. 字符串算法库 Boost.StringAlgorithms

  Boost C++ 字符串算法库 Boost.StringAlgorithms 提供了很多字符串操作函数。 字符串的类型可以是 std::string, std::wstring 或任何其他模板类 std::basic_string 的实例。

  这些函数分类别在不同的头文件定义。 例如,大小写转换函数定义在文件 boost/algorithm/string/case_conv.hpp 中。 因为 Boost.StringAlgorithms 类中包括超过20个类别和相同数目的头文件, 为了方便起见,头文件 boost/algorithm/string.hpp 包括了所有其他的头文件。 后面所有例子都会使用这个头文件。

  正如上节提到的那样, Boost.StringAlgorithms 库中许多函数 都可以接受一个类型为 std::locale 的对象作为附加参数。 而此参数是可选的,如果不设置将使用默认全局区域设置。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 
#include <clocale> 

int main() 
{
    
     
	  std::setlocale(LC_ALL, "German"); 
	  std::string s = "Boris Schäling"; 
	  std::cout << boost::algorithm::to_upper_copy(s) << std::endl; 
	  std::cout << boost::algorithm::to_upper_copy(s, std::locale("German")) << std::endl; 
} 

  函数 boost::algorithm::to_upper_copy() 用于 转换一个字符串为大写形式,自然也有提供相反功能的函数 —— boost::algorithm::to_lower_copy() 把字符串转换为小写形式。 这两个函数都返回转换过的字符串作为结果。 如果作为参数传入的字符串自身需要被转换为大(小)写形式,可以使用函数 boost::algorithm::to_upper() 或 boost::algorithm::to_lower ()。

  上面的例子使用函数 boost::algorithm::to_upper_copy() 把字符串 “Boris Schäling” 转换为大写形式。 第一次调用时使用的是默认全局区域设置, 第二次调用时则明确将区域设置为德国文化。

  显然后者的转换是正确的, 因为小写字母 ‘ä’ 对应的大写形式 ‘Ä’ 是存在的。 而在 C 区域设置中, ‘ä’ 是一个未知字符所以不能转换。 为了能得到正确结果,必须明确传递正确的区域设置参数或者在调用 boost::algorithm::to_upper_copy() 之前改变全局区域设置。

  可以注意到,程序使用了定义在头文件 clocale 中的函数 std::setlocale() 为 C 函数进行区域设置, 因为 std::cout 使用 C 函数在屏幕上显示信息。 在设置了正确的区域后,才可以正确显示 ‘ä’ 和 ‘Ä’ 等元音字母。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "Boris Schäling"; 
	  std::cout << boost::algorithm::to_upper_copy(s) << std::endl; 
	  std::cout << boost::algorithm::to_upper_copy(s, std::locale("German")) << std::endl; 
} 

  上述程序将全局区域设置设为德国文化,这使得对函数 boost::algorithm::to_upper_copy() 的调用 可以将 ‘ä’ 转换为 ‘Ä’ 。

  注意到本例并没有调用函数 std::setlocale() 。 使用函数 std::locale::global() 设置全局区域设置后, 也自动进行了 C 区域设置。 实际上,C++ 程序几乎总是使用函数 std::locale::global() 进行全局区域设置, 而不是像前面的例子那样使用函数 std::setlocale() 。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "Boris Schäling"; 
	  std::cout << boost::algorithm::erase_first_copy(s, "i") << std::endl; 
	  std::cout << boost::algorithm::erase_nth_copy(s, "i", 0) << std::endl; 
	  std::cout << boost::algorithm::erase_last_copy(s, "i") << std::endl; 
	  std::cout << boost::algorithm::erase_all_copy(s, "i") << std::endl; 
	  std::cout << boost::algorithm::erase_head_copy(s, 5) << std::endl; 
	  std::cout << boost::algorithm::erase_tail_copy(s, 8) << std::endl; 
} 

  Boost.StringAlgorithms 库提供了几个从字符串中删除单独字母的函数, 可以明确指定在哪里删除,如何删除。例如,可以使用函数 boost::algorithm::erase_all_copy() 从整个字符串中 删除特定的某个字符。 如果只在此字符首次出现时删除,可以使用函数 boost::algorithm::erase_first_copy() 。 如果要在字符串头部或尾部删除若干字符,可以使用函数 boost::algorithm::erase_head_copy() 和 boost::algorithm::erase_tail_copy() 。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "Boris Schäling"; 
	  boost::iterator_range<std::string::iterator> r = boost::algorithm::find_first(s, "Boris"); 
	  std::cout << r << std::endl; 
	  r = boost::algorithm::find_first(s, "xyz"); 
	  std::cout << r << std::endl; 
} 

  以下各个不同函数 boost::algorithm::find_first()、 boost::algorithm::find_last()、 boost::algorithm::find_nth()、 boost::algorithm::find_head() 以及 boost::algorithm::find_tail() 可以用于在字符串中查找子串。

  所有这些函数的共同点是均返回类型为 boost::iterator_range 类 的一对迭代器。 此类起源于 Boost C++ 的 Boost.Range 库, 它在迭代器的概念上定义了“范围”。 因为操作符 << 由 boost::iterator_range 类重载而来, 单个搜索算法的结果可以直接写入标准输出流。 以上程序将 Boris 作为第一个结果输出而第二个结果为空字符串。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 
#include <vector> 

int main() 
{
    
     
  std::locale::global(std::locale("German")); 
  std::vector<std::string> v; 
  v.push_back("Boris"); 
  v.push_back("Schäling"); 
  std::cout << boost::algorithm::join(v, " ") << std::endl; 
} 

  函数 boost::algorithm::join() 接受一个字符串的容器 作为第一个参数, 根据第二个参数将这些字符串连接起来。 相应地这个例子会输出 Boris Schäling 。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "Boris Schäling"; 
	  std::cout << boost::algorithm::replace_first_copy(s, "B", "D") << std::endl; 
	  std::cout << boost::algorithm::replace_nth_copy(s, "B", 0, "D") << std::endl; 
	  std::cout << boost::algorithm::replace_last_copy(s, "B", "D") << std::endl; 
	  std::cout << boost::algorithm::replace_all_copy(s, "B", "D") << std::endl; 
	  std::cout << boost::algorithm::replace_head_copy(s, 5, "Doris") << std::endl; 
	  std::cout << boost::algorithm::replace_tail_copy(s, 8, "Becker") << std::endl; 
} 

  Boost.StringAlgorithms 库不但提供了查找子串或删除字母的函数, 而且提供了使用字符串替代子串的函数,包括 boost::algorithm::replace_first_copy(), boost::algorithm::replace_nth_copy(), boost::algorithm::replace_last_copy(), boost::algorithm::replace_all_copy(), boost::algorithm::replace_head_copy() 以及 boost::algorithm::replace_tail_copy() 等等。 它们的使用方法同查找和删除函数是差不多一样的,所不同的是还需要 一个替代字符串作为附加参数。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "\t Boris Schäling \t"; 
	  std::cout << "." << boost::algorithm::trim_left_copy(s) << "." << std::endl; 
	  std::cout << "." <<boost::algorithm::trim_right_copy(s) << "." << std::endl; 
	  std::cout << "." <<boost::algorithm::trim_copy(s) << "." << std::endl; 
} 

  可以使用修剪函数 boost::algorithm::trim_left_copy(), boost::algorithm::trim_right_copy() 以及 boost::algorithm::trim_copy() 等自动去除字符串中的空格或者字符串的结束符。 什么字符是空格取决于全局区域设置。

  Boost.StringAlgorithms 库的函数可以接受一个附加的谓词参数,以决定函数作用于字符串的哪些字符。 谓词版本的修剪函数相应地被命名为 boost::algorithm::trim_left_copy_if(), boost::algorithm::trim_right_copy_if() 和 boost::algorithm::trim_copy_if() 。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "--Boris Schäling--"; 
	  std::cout << "." << boost::algorithm::trim_left_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl; 
	  std::cout << "." <<boost::algorithm::trim_right_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl; 
	  std::cout << "." <<boost::algorithm::trim_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl; 
} 

  以上程序调用了一个辅助函数 boost::algorithm::is_any_of() , 它用于生成谓词以验证作为参数传入的字符是否在给定的字符串中存在。使用函数 boost::algorithm::is_any_of 后,正如例子中做的那样,修剪字符串的字符被指定为连字符。 ()

  Boost.StringAlgorithms 类也提供了众多返回通用谓词的辅助函数。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
  std::locale::global(std::locale("German")); 
  std::string s = "123456789Boris Schäling123456789"; 
  std::cout << "." << boost::algorithm::trim_left_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl; 
  std::cout << "." <<boost::algorithm::trim_right_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl; 
  std::cout << "." <<boost::algorithm::trim_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl; 
} 

  函数 boost::algorithm::is_digit() 返回的谓词在字符为数字时返回布尔值 true。 检查字符是否为大写或小写的辅助函数分别是 boost::algorithm::is_upper() 和 boost::algorithm::is_lower() 。 所有这些函数都默认使用全局区域设置,除非在参数中指定其他区域设置。

  除了检验单独字符的谓词之外, Boost.StringAlgorithms 库还提供了处理字符串的函数。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  std::cout << boost::algorithm::starts_with(s, "Boris") << std::endl; 
  std::cout << boost::algorithm::ends_with(s, "Schäling") << std::endl; 
  std::cout << boost::algorithm::contains(s, "is") << std::endl; 
  std::cout << boost::algorithm::lexicographical_compare(s, "Boris") << std::endl; 
} 

  函数 boost::algorithm::starts_with()、 boost::algorithm::ends_with()、 boost::algorithm::contains() 和 boost::algorithm::lexicographical_compare() 均可以比较两个字符串。

  以下介绍一个字符串切割函数。

#include <boost/algorithm/string.hpp> 
#include <locale> 
#include <iostream> 
#include <vector> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "Boris Schäling"; 
	  std::vector<std::string> v; 
	  boost::algorithm::split(v, s, boost::algorithm::is_space()); 
	  std::cout << v.size() << std::endl; 
} 

  在给定分界符后,使用函数 boost::algorithm::split() 可以将一个字符串拆分为一个字符串容器。 它需要给定一个谓词作为第三个参数以判断应该在字符串的哪个位置分割。 这个例子使用了辅助函数 boost::algorithm::is_space() 创建一个谓词,在每个空格字符处分割字符串。

  本节中许多函数都有忽略字符串大小写的版本, 这些版本一般都有与原函数相似的名称,所相差的只是以 ‘i’.开头。 例如,与函数 boost::algorithm::erase_all_copy() 相对应的是函数 boost::algorithm::ierase_all_copy()。

  最后,值得注意的是类 Boost.StringAlgorithms 中许多函数都支持正则表达式。 以下程序使用函数 boost::algorithm::find_regex() 搜索正则表达式。

#include <boost/algorithm/string.hpp> 
#include <boost/algorithm/string/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
  std::locale::global(std::locale("German")); 
  std::string s = "Boris Schäling"; 
  boost::iterator_range<std::string::iterator> r = boost::algorithm::find_regex(s, boost::regex("\\w\\s\\w")); 
  std::cout << r << std::endl; 
} 

  为了使用正则表达式,此程序使用了Boost C++ 库中的 boost::regex , 这将在下一节介绍。

5.4. 正则表达式库 Boost.Regex

  Boost C++ 的正则表达式库 Boost.Regex 可以应用正则表达式于 C++ 。 正则表达式大大减轻了搜索特定模式字符串的负担,在很多语言中都是强大的功能。 虽然现在 C++ 仍然需要以 Boost C++ 库的形式提供这一功能,但是在将来正则表达式将进入 C++ 标准库。 Boost.Regex 库有望包括在下一版的 C++ 标准中。

  Boost.Regex 库中两个最重要的类是 boost::regex 和 boost::smatch, 它们都在 boost/regex.hpp 文件中定义。 前者用于定义一个正则表达式,而后者可以保存搜索结果。

  以下将要介绍 Boost.Regex 库中提供的三个搜索正则表达式的函数。

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "Boris Schäling"; 
	  boost::regex expr("\\w+\\s\\w+"); 
	  std::cout << boost::regex_match(s, expr) << std::endl; 
} 

  函数 boost::regex_match() 用于字符串与正则表达式的比较。 在整个字符串匹配正则表达式时其返回值为 true 。

  函数 boost::regex_search() 可用于在字符串中搜索正则表达式。

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "Boris Schäling"; 
	  boost::regex expr("(\\w+)\\s(\\w+)"); 
	  boost::smatch what; 
	  if (boost::regex_search(s, what, expr)) 
	  {
    
     
		    std::cout << what[0] << std::endl; 
		    std::cout << what[1] << " " << what[2] << std::endl; 
	  } 
} 

  函数 boost::regex_search() 可以接受一个类型为 boost::smatch 的引用的参数用于储存结果。 函数 boost::regex_search() 只用于分类的搜索, 本例实际上返回了两个结果, 它们是基于正则表达式的分组。

  存储结果的类 boost::smatch 事实上是持有类型为 boost::sub_match 的元素的容器, 可以通过与类 std::vector 相似的界面访问。 例如, 元素可以通过操作符 operator 访问。

  另一方面,类 boost::sub_match 将迭代器保存在对应于正则表达式分组的位置。 因为它继承自类 std::pair ,迭代器引用的子串可以使用 first 和 second 访问。如果像上面的例子那样,只把子串写入标准输出流, 那么通过重载操作符 << 就可以直接做到这一点,那么并不需要访问迭代器。

  请注意结果保存在迭代器中而 boost::sub_match 类并不复制它们, 这说明它们只是在被迭代器引用的相关字符串存在时才可以访问。

  另外,还需要注意容器 boost::smatch 的第一个元素存储的引用是指向匹配正则表达式的整个字符串的,匹配第一组的第一个子串由索引 1 访问。

  Boost.Regex 提供的第三个函数是 boost::regex_replace()。

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = " Boris Schäling "; 
	  boost::regex expr("\\s"); 
	  std::string fmt("_"); 
	  std::cout << boost::regex_replace(s, expr, fmt) << std::endl; 
} 

  除了待搜索的字符串和正则表达式之外, boost::regex_replace() 函数还需要一个格式参数,它决定了子串、匹配正则表达式的分组如何被替换。如果正则表达式不包含任何分组,相关子串将被用给定的格式一个个地被替换。这样上面程序输出的结果为 Boris_Schäling

  boost::regex_replace() 函数总是在整个字符串中搜索正则表达式,所以这个程序实际上将三处空格都替换为下划线。

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "Boris Schäling"; 
	  boost::regex expr("(\\w+)\\s(\\w+)"); 
	  std::string fmt("\\2 \\1"); 
	  std::cout << boost::regex_replace(s, expr, fmt) << std::endl; 
} 

  格式参数可以访问由正则表达式分组的子串,这个例子正是使用了这项技术,交换了姓、名的位置,于是结果显示为 Schäling Boris 。

  需要注意的是,对于正则表达式和格式有不同的标准。 这三个函数都可以接受一个额外的参数,用于选择具体的标准。 也可以指定是否以某一具体格式解释特殊字符或者替代匹配正则表达式的整个字符串。

#include <boost/regex.hpp> 
#include <locale> 
#include <iostream> 

int main() 
{
    
     
	  std::locale::global(std::locale("German")); 
	  std::string s = "Boris Schäling"; 
	  boost::regex expr("(\\w+)\\s(\\w+)"); 
	  std::string fmt("\\2 \\1"); 
	  std::cout << boost::regex_replace(s, expr, fmt, boost::regex_constants::format_literal) << std::endl; 
} 

  此程序将 boost::regex_constants::format_literal 标志作为第四参数传递给函数 boost::regex_replace() ,从而抑制了格式参数中对特殊字符的处理。 因为整个字符串匹配正则表达式,所以本例中经格式参数替换的到达的输出结果为 \2 \1。

  正如上一节末指出的那样,正则表达式可以和 Boost.StringAlgorithms 库结合使用。它通过 Boost.Regex 库提供函数如 boost::algorithm::find_regex() 、 boost::algorithm::replace_regex() 、 boost::algorithm::erase_regex() 以及 boost::algorithm::split_regex() 等等。由于 Boost.Regex 库很有可能成为即将到来的下一版 C++ 标准的一部分,脱离 Boost.StringAlgorithms 库,熟练地使用正则表达式是个明智的选择。

5.5. 词汇分割器库 Boost.Tokenizer

  Boost.Tokenizer 库可以在指定某个字符为分隔符后,遍历字符串的部分表达式。

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{
    
     
	  typedef boost::tokenizer<boost::char_separator<char> > tokenizer; 
	  std::string s = "Boost C++ libraries"; 
	  tokenizer tok(s); 
	  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
	    	std::cout << *it << std::endl; 
} 

  Boost.Tokenizer 库在 boost/tokenizer.hpp 文件中定义了模板类 boost::tokenizer ,其模板参数为支持相关表达式的类。 上面的例子中就使用了 boost::char_separator 类作为模板参数,它将空格和标点符号视为分隔符。

  词汇分割器必须由类型为 std::string 的字符串初始化。通过使用 begin() 和 end() 方法,词汇分割器可以像容器一样访问。通过使用迭代器,可以得到前述字符串的部分表达式。模板参数的类型决定了如何达到部分表达式。

  因为 boost::char_separator 类默认将空格和标点符号视为分隔符,所以本例显示的结果为 Boost 、 C 、 + 、 + 和 libraries 。 为了识别这些分隔符, boost::char_separator 函数调用了 std::isspace() 函数和 std::ispunct 函数。 ()Boost.Tokenizer 库会区分要隐藏的分隔符和要显示的分隔符。 在默认的情况下,空格会隐藏而标点符号会显示出来,所以这个例子里显示了两个加号。

  如果不需要将标点符号作为分隔符,可以在传递给词汇分割器之前相应地初始化 boost::char_separator 对象。 以下例子正式这样做的。

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{
    
     
	  typedef boost::tokenizer<boost::char_separator<char> > tokenizer; 
	  std::string s = "Boost C++ libraries"; 
	  boost::char_separator<char> sep(" "); 
	  tokenizer tok(s, sep); 
	  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
	    	std::cout << *it << std::endl; 
} 

  类 boost::char_separator 的构造函数可以接受三个参数, 只有第一个是必须的,它描述了需要隐藏的分隔符。 在本例中, 空格仍然被视为分隔符。

  第二个参数指定了需要显示的分隔符。 在不提供此参数的情况下,将不显示任何分隔符。 执行程序,会显示 Boost 、 C++ 和 libraries 。

  如果将加号作为第二个参数,此例的结果将和上一个例子相同。

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{
    
     
	  typedef boost::tokenizer<boost::char_separator<char> > tokenizer; 
	  std::string s = "Boost C++ libraries"; 
	  boost::char_separator<char> sep(" ", "+"); 
	  tokenizer tok(s, sep); 
	  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
	    std::cout << *it << std::endl; 
} 

  第三个参数决定了是否显示空的部分表达式。 如果连续找到两个分隔符,他们之间的部分表达式将为空。在默认情况下,这些空表达式是不会显示的。第三个参数可以改变默认的行为。

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{
    
     
	  typedef boost::tokenizer<boost::char_separator<char> > tokenizer; 
	  std::string s = "Boost C++ libraries"; 
	  boost::char_separator<char> sep(" ", "+", boost::keep_empty_tokens); 
	  tokenizer tok(s, sep); 
	  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
	    	std::cout << *it << std::endl; 
} 

  执行以上程序,会显示另外两个的空表达式。 其中第一个是在两个加号中间的而第二个是加号和之后的空格之间的。

词汇分割器也可用于不同的字符串类型。

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{
    
     
	  typedef boost::tokenizer<boost::char_separator<wchar_t>, std::wstring::const_iterator, std::wstring> tokenizer; 
	  std::wstring s = L"Boost C++ libraries"; 
	  boost::char_separator<wchar_t> sep(L" "); 
	  tokenizer tok(s, sep); 
	  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
	    	std::wcout << *it << std::endl; 
} 

  这个例子遍历了一个类型为 std::wstring 的字符串。 为了使用这个类型的字符串,必须使用另外的模板参数初始化词汇分割器,对 boost::char_separator 类也是如此,他们都需要参数 wchar_t 初始化。

  除了 boost::char_separator 类之外, Boost.Tokenizer 还提供了另外两个类以识别部分表达式。

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{
    
     
	  typedef boost::tokenizer<boost::escaped_list_separator<char> > tokenizer; 
	  std::string s = "Boost,\"C++ libraries\""; 
	  tokenizer tok(s); 
	  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
	    	std::cout << *it << std::endl; 
} 

  boost::escaped_list_separator 类用于读取由逗号分隔的多个值,这个格式的文件通常称为 CSV (comma separated values,逗号分隔文件),它甚至还可以处理双引号以及转义序列。所以本例的输出为 Boost 和 C++ libraries 。

  另一个是 boost::offset_separator 类,必须用实例说明。 这个类的对象必须作为第二个参数传递给 boost::tokenizer 类的构造函数。

#include <boost/tokenizer.hpp> 
#include <string> 
#include <iostream> 

int main() 
{
    
     
  typedef boost::tokenizer<boost::offset_separator> tokenizer; 
  std::string s = "Boost C++ libraries"; 
  int offsets[] = {
    
     5, 5, 9 }; 
  boost::offset_separator sep(offsets, offsets + 3); 
  tokenizer tok(s, sep); 
  for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) 
    std::cout << *it << std::endl; 
} 

  boost::offset_separator 指定了部分表达式应当在字符串中的哪个位置结束。 以上程序制定第一个部分表达式在 5 个字符后结束,第二个字符串在另 5 个字符后结束,第三个也就是最后一个字符串应当在之后的 9 个字符后结束。 输出的结果为 Boost 、 C++ 和 libraries 。

5.6. 格式化输出库 Boost.Format

  Boost.Format 库可以作为定义在文件 cstdio 中的函数 std::printf() 的替代。 std::printf() 函数最初出现在 C 标准中,提供格式化数据输出功能, 但是它既不是类型安全的有不能扩展。 因此在 C++ 应用中, Boost.Format 库通常是数据格式化输出的上佳之选。

  Boost.Format 库在文件 boost/format.hpp 中定义了类 boost::format 。 与函数 std::printf 相似的是,传递给() boost::format 的构造函数的参数也是一个字符串,它由控制格式的特殊字符组成。 实际数据通过操作符 % 相连,在输出中替代特殊字符,如下例所示。

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{
    
     
  	std::cout << boost::format("%1%.%2%.%3%") % 16 % 9 % 2008 << std::endl; 
} 

  Boost.Format 类使用置于两个百分号之间的数字作为占位符,占位符稍后通过 % 操作符与实际数据连接。 以上程序使用数字16、9 和 2009 组成一个日期字符串,以 16.9.2008的格式输出。 如果要月份出现在日期之前,即美式表示,只需交换占位符即可。

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{
    
     
  	std::cout << boost::format("%2%/%1%/%3%") % 16 % 9 % 2008 << std::endl; 
} 

  现在程序显示的结果变成 9/16/2008 。

  如果要使用C++ 操作器格式化数据,Boost.Format 库提供了函数 boost::io::group() 。

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{
    
     
  	std::cout << boost::format("%1% %2% %1%") % boost::io::group(std::showpos, 99) % 100 << std::endl; 
} 

  本例的结果显示为 +99 100 +99 。 因为操作器 std::showpos() 通过 boost::io::group() 与数字 99 连接,所以只要显示 99 , 在它前面就会自动加上加号。

如果需要加号仅在 99 第一次输出时显示, 则需要改造格式化占位符。

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{
    
     
 	 std::cout << boost::format("%|1$+| %2% %1%") % 99 % 100 << std::endl; 
} 

  为了将输出格式改为 +99 100 99 ,不但需要将数据的引用符号由 1$ 变为 1% ,还需要在其两侧各添加一个附加的管道符号,即将占位符 %1% 替换为 %|1$+|。

  请注意,虽然一般对数据的引用不是必须的,但是所有占位符一定要同时设置为指定货非指定。 以下例子在执行时会出现错误,因为它给第二个和第三个占位符设置了引用但是却忽略了第一个。

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{
    
     
	  try 
	  {
    
     
	   	 std::cout << boost::format("%|+| %2% %1%") % 99 % 100 << std::endl; 
	  } 
	  catch (boost::io::format_error &ex) 
	  {
    
     
	   	 std::cout << ex.what() << std::endl; 
	  } 
} 

  此程序抛出了类型为 boost::io::format_error 的异常。 严格地说,Boost.Format 库抛出的异常为 boost::io::bad_format_string。 但是由于所有的异常类都继承自 boost::io::format_error 类,捕捉此类型的异常会轻松一些。

  以下例子演示了不引用数据的方法。

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{
    
     
  	std::cout << boost::format("%|+| %|| %||") % 99 % 100 % 99 << std::endl; 
} 

  第二、第三个占位符的管道符号可以被安全地省略,因为在这种情况下,他们并不指定格式。这样的语法看起来很像 std::printf ()的那种。

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{
    
     
 	 std::cout << boost::format("%+d %d %d") % 99 % 100 % 99 << std::endl; 
} 

  虽然这看起来就像 std::printf() ,但是 Boost.Format 库有类型安全的优点。 格式化字符串中字母 ‘d’ 的使用并不表示输出数字,而是表示 boost::format 类所使用的内部流对象上的 std::dec() 操作器,它可以使用某些对 std::printf() 函数无意义的格式字符串,如果使用 std::printf() 会导致程序在运行时崩溃。

#include <boost/format.hpp> 
#include <iostream> 

int main() 
{
    
     
  	std::cout << boost::format("%+s %s %s") % 99 % 100 % 99 << std::endl; 
} 

  尽管在 std::printf() 函数中,字母 ‘s’ 只用于表示类型为 const char* 的字符串,然而以上程序却能正常运行。 因为在 Boost.Format 库中,这并不代表强制为字符串,它会结合适当的操作器,调整内部流的操作模式。 所以即使在这种情况下, 在内部流中加入数字也是没问题的。

第 6 章 多线程

6.1. 概述

  线程就是,在同一程序同一时间内允许执行不同函数的离散处理队列。 这使得一个长时间去进行某种特殊运算的函数在执行时不阻碍其他的函数变得十分重要。 线程实际上允许同时执行两种函数,而这两个函数不必相互等待。

  一旦一个应用程序启动,它仅包含一个默认线程。 此线程执行 main() 函数。 在 main()中被调用的函数则按这个线程的上下文顺序地执行。 这样的程序称为单线程程序。

  反之,那些创建新的线程的程序就是多线程程序。 他们不仅可以在同一时间执行多个函数,而且这在如今多核盛行的时代显得尤为重要。 既然多核允许同时执行多个函数,这就使得对开发人员相应地使用这种处理能力提出了要求。 然而线程一直被用来当并发地执行多个函数,开发人员现在不得不仔细地构建应用来支持这种并发。 多线程编程知识也因此在多核系统时代变得越来越重要。

  本章将介绍C++ Boost库 Boost.Thread,它可以开发独立于平台的多线程应用程序。

6.2. 线程管理

  在这个库最重要的一个类就是 boost::thread,它是在 boost/thread.hpp 里定义的,用来创建一个新线程。下面的示例来说明如何运用它。

#include <boost/thread.hpp> 
#include <iostream> 

void wait(int seconds) 
{
    
     
	  boost::this_thread::sleep(boost::posix_time::seconds(seconds)); 
} 

void thread() 
{
    
     
	  for (int i = 0; i < 5; ++i) 
	  {
    
     
	    wait(1); 
	    std::cout << i << std::endl; 
	  } 
} 

int main() 
{
    
     
	  boost::thread t(thread); 
	  t.join(); 
} 

  新建线程里执行的那个函数的名称被传递到 boost::thread 的构造函数。 一旦上述示例中的变量 t 被创建,该 thread() 函数就在其所在线程中被立即执行。 同时在 main() 里也并发地执行该 thread() 。

  为了防止程序终止,就需要对新建线程调用 join() 方法。 join() 方法是一个阻塞调用:它可以暂停当前线程,直到调用 join() 的线程运行结束。 这就使得 main() 函数一直会等待到 thread() 运行结束。

  正如在上面的例子中看到,一个特定的线程可以通过诸如 t 的变量访问,通过这个变量等待着它的使用 join() 方法终止。 但是,即使 t 越界或者析构了,该线程也将继续执行。 一个线程总是在一开始就绑定到一个类型为 boost::thread 的变量,但是一旦创建,就不在取决于它。 甚至还存在着一个叫 detach() 的方法,允许类型为 boost::thread 的变量从它对应的线程里分离。 当然了,像 join() 的方法之后也就不能被调用,因为这个变量不再是一个有效的线程。

  任何一个函数内可以做的事情也可以在一个线程内完成。 归根结底,一个线程只不过是一个函数,除了它是同时执行的。 在上述例子中,使用一个循环把5个数字写入标准输出流。 为了减缓输出,每一个循环中调用 wait() 函数让执行延迟了一秒。 wait() 可以调用一个名为 sleep() 的函数,这个函数也来自于 Boost.Thread,位于 boost::this_thread 名空间内。

  sleep() 要么在预计的一段时间或一个特定的时间点后时才让线程继续执行。 通过传递一个类型为 boost::posix_time::seconds 的对象,在这个例子里我们指定了一段时间。 boost::posix_time::seconds 来自于 Boost.DateTime 库,它被 Boost.Thread 用来管理和处理时间的数据。

  虽然前面的例子说明了如何等待一个不同的线程,但下面的例子演示了如何通过所谓的中断点让一个线程中断。

#include <boost/thread.hpp> 
#include <iostream> 

void wait(int seconds) 
{
    
     
	  boost::this_thread::sleep(boost::posix_time::seconds(seconds)); 
} 

void thread() 
{
    
     
	  try 
	  {
    
     
	    for (int i = 0; i < 5; ++i) 
	    {
    
     
	      	wait(1); 
	      	std::cout << i << std::endl; 
	    } 
	  } 
	  catch (boost::thread_interrupted&) 
	  {
    
     
	  } 
} 

int main() 
{
    
     
	  boost::thread t(thread); 
	  wait(3); 
	  t.interrupt(); 
	  t.join(); 
} 

  在一个线程对象上调用 interrupt() 会中断相应的线程。 在这方面,中断意味着一个类型为 boost::thread_interrupted 的异常,它会在这个线程中抛出。 然后这只有在线程达到中断点时才会发生。

  如果给定的线程不包含任何中断点,简单调用 interrupt() 就不会起作用。 每当一个线程中断点,它就会检查 interrupt() 是否被调用过。 只有被调用过了, boost::thread_interrupted 异常才会相应地抛出。

  Boost.Thread定义了一系列的中断点,例如 sleep() 函数。 由于 sleep() 在这个例子里被调用了五次,该线程就检查了五次它是否应该被中断。 然而 sleep() 之间的调用,却不能使线程中断。

  一旦该程序被执行,它只会打印三个数字到标准输出流。 这是由于在main里3秒后调用 interrupt()方法。 因此,相应的线程被中断,并抛出一个 boost::thread_interrupted 异常。 这个异常在线程内也被正确地捕获, catch 处理虽然是空的。 由于 thread() 函数在处理程序后返回,线程也被终止。 这反过来也将终止整个程序,因为 main() 等待该线程使用join()终止该线程。

  Boost.Thread定义包括上述 sleep()函数十个中断。 有了这些中断点,线程可以很容易及时中断。 然而,他们并不总是最佳的选择,因为中断点必须事前读入以检查 boost::thread_interrupted 异常。

  为了提供一个对 Boost.Thread 里提供的多种函数的整体概述,下面的例子将会再介绍两个。

#include <boost/thread.hpp> 
#include <iostream> 

int main() 
{
    
     
	  std::cout << boost::this_thread::get_id() << std::endl; 
	  std::cout << boost::thread::hardware_concurrency() << std::endl; 
} 

  使用 boost::this_thread命名空间,能提供独立的函数应用于当前线程,比如前面出现的 sleep() 。 另一个是 get_id():它会返回一个当前线程的ID号。 它也是由 boost::thread 提供的。

  boost::thread 类提供了一个静态方法 hardware_concurrency() ,它能够返回基于CPU数目或者CPU内核数目的刻在同时在物理机器上运行的线程数。 在常用的双核机器上调用这个方法,返回值为2。 这样的话就可以确定在一个多核程序可以同时运行的理论最大线程数。

6.3. 同步

  虽然多线程的使用可以提高应用程序的性能,但也增加了复杂性。 如果使用线程在同一时间执行几个函数,访问共享资源时必须相应地同步。 一旦应用达到了一定规模,这涉及相当一些工作。 本段介绍了Boost.Thread提供同步线程的类。

#include <boost/thread.hpp> 
#include <iostream> 

void wait(int seconds) 
{
    
     
	  boost::this_thread::sleep(boost::posix_time::seconds(seconds)); 
} 

boost::mutex mutex; 

void thread() 
{
    
     
	  for (int i = 0; i < 5; ++i) 
	  {
    
     
		    wait(1); 
		    mutex.lock(); 
		    std::cout << "Thread " << boost::this_thread::get_id() << ": " << i << std::endl; 
		    mutex.unlock(); 
	  } 
} 

int main() 
{
    
    
	  boost::thread t1(thread); 
	  boost::thread t2(thread); 
	  t1.join(); 
	  t2.join(); 
} 

  多线程程序使用所谓的互斥对象来同步。 Boost.Thread提供多个的互斥类,boost::mutex是最简单的一个。 互斥的基本原则是当一个特定的线程拥有资源的时候防止其他线程夺取其所有权。 一旦释放,其他的线程可以取得所有权。 这将导致线程等待至另一个线程完成处理一些操作,从而相应地释放互斥对象的所有权。

  上面的示例使用一个类型为 boost::mutex 的 mutex 全局互斥对象。 thread() 函数获取此对象的所有权才在 for 循环内使用 lock() 方法写入到标准输出流的。 一旦信息被写入,使用 unlock() 方法释放所有权。

  main() 创建两个线程,同时执行 thread ()函数。 利用 for 循环,每个线程数到5,用一个迭代器写一条消息到标准输出流。 不幸的是,标准输出流是一个全局性的被所有线程共享的对象。 该标准不提供任何保证 std::cout 可以安全地从多个线程访问。 因此,访问标准输出流必须同步:在任何时候,只有一个线程可以访问 std::cout。

  由于两个线程试图在写入标准输出流前获得互斥体,实际上只能保证一次只有一个线程访问 std::cout。 不管哪个线程成功调用 lock() 方法,其他所有线程必须等待,直到 unlock() 被调用。

  获取和释放互斥体是一个典型的模式,是由Boost.Thread通过不同的数据类型支持。 例如,不直接地调用 lock() 和 unlock(),使用 boost::lock_guard 类也是可以的。

#include <boost/thread.hpp> 
#include <iostream> 

void wait(int seconds) 
{
    
     
	  boost::this_thread::sleep(boost::posix_time::seconds(seconds)); 
} 

boost::mutex mutex; 

void thread() 
{
    
     
	  for (int i = 0; i < 5; ++i) 
	  {
    
     
		    wait(1); 
		    boost::lock_guard<boost::mutex> lock(mutex); 
		    std::cout << "Thread " << boost::this_thread::get_id() << ": " << i << std::endl; 
	  } 
} 

int main() 
{
    
     
	  boost::thread t1(thread); 
	  boost::thread t2(thread); 
	  t1.join(); 
	  t2.join(); 
} 

  boost::lock_guard 在其内部构造和析构函数分别自动调用 lock() 和 unlock() 。 访问共享资源是需要同步的,因为它显示地被两个方法调用。 boost::lock_guard 类是另一个出现在 第 2 章 智能指针 的RAII用语。

  除了boost::mutex 和 boost::lock_guard 之外,Boost.Thread也提供其他的类支持各种同步。 其中一个重要的就是 boost::unique_lock ,相比较 boost::lock_guard 而言,它提供许多有用的方法。

#include <boost/thread.hpp> 
#include <iostream> 

void wait(int seconds) 
{
    
     
	  boost::this_thread::sleep(boost::posix_time::seconds(seconds)); 
} 

boost::timed_mutex mutex; 

void thread() 
{
    
     
	  for (int i = 0; i < 5; ++i) 
	  {
    
     
		    wait(1); 
		    boost::unique_lock<boost::timed_mutex> lock(mutex, boost::try_to_lock); 
		    if (!lock.owns_lock()) {
    
    
		      lock.timed_lock(boost::get_system_time() + boost::posix_time::seconds(1)); 
		    }
		    std::cout << "Thread " << boost::this_thread::get_id() << ": " << i << std::endl; 
		    boost::timed_mutex *m = lock.release(); 
		    m->unlock(); 
	  } 
} 

int main() 
{
    
     
	  boost::thread t1(thread); 
	  boost::thread t2(thread); 
	  t1.join(); 
	  t2.join(); 
} 

  上面的例子用不同的方法来演示 boost::unique_lock 的功能。 当然了,这些功能的用法对给定的情景不一定适用;boost::lock_guard 在上个例子的用法还是挺合理的。 这个例子就是为了演示 boost::unique_lock 提供的功能。

  boost::unique_lock 通过多个构造函数来提供不同的方式获得互斥体。 这个期望获得互斥体的函数简单地调用了 lock() 方法,一直等到获得这个互斥体。 所以它的行为跟 boost::lock_guard 的那个是一样的。

  如果第二个参数传入一个 boost::try_to_lock 类型的值,对应的构造函数就会调用 try_lock() 方法。 这个方法返回 bool 型的值:如果能够获得互斥体则返回true,否则返回 false 。 相比 lock() 函数,try_lock() 会立即返回,而且在获得互斥体之前不会被阻塞。

  上面的程序向 boost::unique_lock 的构造函数的第二个参数传入boost::try_to_lock。 然后通过 owns_lock() 可以检查是否可获得互斥体。 如果不能, owns_lock() 返回 false。 这也用到 boost::unique_lock 提供的另外一个函数: timed_lock() 等待一定的时间以获得互斥体。 给定的程序等待长达1秒,应较足够的时间来获取更多的互斥。

  其实这个例子显示了三个方法获取一个互斥体:lock() 会一直等待,直到获得一个互斥体。 try_lock() 则不会等待,但如果它只会在互斥体可用的时候才能获得,否则返回 false 。 最后,timed_lock() 试图获得在一定的时间内获取互斥体。 和 try_lock() 一样,返回bool 类型的值意味着成功是否。

  虽然 boost::mutex 提供了 lock() 和 try_lock() 两个方法,但是 boost::timed_mutex 只支持 timed_lock() ,这就是上面示例那么使用的原因。 如果不用 timed_lock() 的话,也可以像以前的例子那样用 boost::mutex。

  就像 boost::lock_guard 一样, boost::unique_lock 的析构函数也会相应地释放互斥量。此外,可以手动地用 unlock() 释放互斥量。也可以像上面的例子那样,通过调用 release() 解除boost::unique_lock 和互斥量之间的关联。然而在这种情况下,必须显式地调用 unlock() 方法来释放互斥量,因为 boost::unique_lock 的析构函数不再做这件事情。

  boost::unique_lock 这个所谓的独占锁意味着一个互斥量同时只能被一个线程获取。 其他线程必须等待,直到互斥体再次被释放。 除了独占锁,还有非独占锁。 Boost.Thread里有个 boost::shared_lock 的类提供了非独占锁。 正如下面的例子,这个类必须和 boost::shared_mutex 型的互斥量一起使用。

#include <boost/thread.hpp> 
#include <iostream> 
#include <vector> 
#include <cstdlib> 
#include <ctime> 

void wait(int seconds) 
{
    
     
	  boost::this_thread::sleep(boost::posix_time::seconds(seconds)); 
} 

boost::shared_mutex mutex; 
std::vector<int> random_numbers; 

void fill() 
{
    
     
	  std::srand(static_cast<unsigned int>(std::time(0))); 
	  for (int i = 0; i < 3; ++i) 
	  {
    
     
		    boost::unique_lock<boost::shared_mutex> lock(mutex); 
		    random_numbers.push_back(std::rand()); 
		    lock.unlock(); 
		    wait(1); 
	  } 
} 

void print() 
{
    
     
	  for (int i = 0; i < 3; ++i) 
	  {
    
     
		    wait(1); 
		    boost::shared_lock<boost::shared_mutex> lock(mutex); 
		    std::cout << random_numbers.back() << std::endl; 
	  } 
} 

int sum = 0; 

void count() 
{
    
     
	  for (int i = 0; i < 3; ++i) 
	  {
    
     
		    wait(1); 
		    boost::shared_lock<boost::shared_mutex> lock(mutex); 
		    sum += random_numbers.back(); 
	  } 
} 

int main() 
{
    
     
	  boost::thread t1(fill); 
	  boost::thread t2(print); 
	  boost::thread t3(count); 
	  t1.join(); 
	  t2.join(); 
	  t3.join(); 
	  std::cout << "Sum: " << sum << std::endl; 
} 

  boost::shared_lock 类型的非独占锁可以在线程只对某个资源读访问的情况下使用。 一个线程修改的资源需要写访问,因此需要一个独占锁。 这样做也很明显:只需要读访问的线程不需要知道同一时间其他线程是否访问。 因此非独占锁可以共享一个互斥体。

  在给定的例子, print() 和 count() 都可以只读访问 random_numbers 。 虽然 print() 函数把 random_numbers 里的最后一个数写到标准输出,count() 函数把它统计到 sum 变量。 由于没有函数修改 random_numbers,所有的都可以在同一时间用 boost::shared_lock 类型的非独占锁访问它。

  在 fill() 函数里,需要用一个 boost::unique_lock 类型的非独占锁,因为它插入了一个新的随机数到 random_numbers。 在 unlock() 显式地调用 unlock() 来释放互斥量之后, fill() 等待了一秒。 相比于之前的那个样子, 在 for 循环的尾部调用 wait() 以保证容器里至少存在一个随机数,可以被print() 或者 count() 访问。 对应地,这两个函数在 for 循环的开始调用了 wait() 。

  考虑到在不同的地方每个单独地调用 wait() ,一个潜在的问题变得很明显:函数调用的顺序直接受CPU执行每个独立进程的顺序决定。 利用所谓的条件变量,可以同步哪些独立的线程,使数组的每个元素都被不同的线程立即添加到 random_numbers 。

#include <boost/thread.hpp> 
#include <iostream> 
#include <vector> 
#include <cstdlib> 
#include <ctime> 

boost::mutex mutex; 
boost::condition_variable_any cond; 
std::vector<int> random_numbers; 

void fill() 
{
    
     
	  std::srand(static_cast<unsigned int>(std::time(0))); 
	  for (int i = 0; i < 3; ++i) 
	  {
    
     
		    boost::unique_lock<boost::mutex> lock(mutex); 
		    random_numbers.push_back(std::rand()); 
		    cond.notify_all(); 
		    cond.wait(mutex); 
	  } 
} 

void print() 
{
    
     
	  std::size_t next_size = 1; 
	  for (int i = 0; i < 3; ++i) 
	  {
    
     
		    boost::unique_lock<boost::mutex> lock(mutex); 
		    while (random_numbers.size() != next_size) 
		      cond.wait(mutex); 
		    std::cout << random_numbers.back() << std::endl; 
		    ++next_size; 
		    cond.notify_all(); 
	  } 
} 

int main() 
{
    
     
	  boost::thread t1(fill); 
	  boost::thread t2(print); 
	  t1.join(); 
	  t2.join(); 
} 

  这个例子的程序删除了 wait() 和 count() 。线程不用在每个循环迭代中等待一秒,而是尽可能快地执行。此外,没有计算总额;数字完全写入标准输出流。

  为确保正确地处理随机数,需要一个允许检查多个线程之间特定条件的条件变量来同步不每个独立的线程。

  正如上面所说, fill() 函数用在每个迭代产生一个随机数,然后放在 random_numbers 容器中。 为了防止其他线程同时访问这个容器,就要相应得使用一个排它锁。 不是等待一秒,实际上这个例子却用了一个条件变量。 调用 notify_all() 会唤醒每个哪些正在分别通过调用wait() 等待此通知的线程。

  通过查看 print() 函数里的 for 循环,可以看到相同的条件变量被 wait() 函数调用了。 如果这个线程被 notify_all() 唤醒,它就会试图这个互斥量,但只有在 fill() 函数完全释放之后才能成功。

  这里的窍门就是调用 wait() 会释放相应的被参数传入的互斥量。 在调用 notify_all()后, fill() 函数会通过 wait() 相应地释放线程。 然后它会阻止和等待其他的线程调用 notify_all() ,一旦随机数已写入标准输出流,这就会在 print() 里发生。

  注意到在 print() 函数里调用 wait() 事实上发生在一个单独 while 循环里。 这样做的目的是为了处理在 print() 函数里第一次调用 wait() 函数之前随机数已经放到容器里。 通过比较 random_numbers 里元素的数目与预期值,发现这成功地处理了把随机数写入到标准输出流。

6.4. 线程本地存储

  线程本地存储(TLS)是一个只能由一个线程访问的专门的存储区域。 TLS的变量可以被看作是一个只对某个特定线程而非整个程序可见的全局变量。 下面的例子显示了这些变量的好处。

#include <boost/thread.hpp> 
#include <iostream> 
#include <cstdlib> 
#include <ctime> 

void init_number_generator() 
{
    
     
	  static bool done = false; 
	  if (!done) 
	  {
    
     
		    done = true; 
		    std::srand(static_cast<unsigned int>(std::time(0))); 
	  } 
} 

boost::mutex mutex; 

void random_number_generator() 
{
    
     
	  init_number_generator(); 
	  int i = std::rand(); 
	  boost::lock_guard<boost::mutex> lock(mutex); 
	  std::cout << i << std::endl; 
} 

int main() 
{
    
     
	  boost::thread t[3]; 
	
	  for (int i = 0; i < 3; ++i) {
    
    
	    t[i] = boost::thread(random_number_generator); 
	  }
	
	  for (int i = 0; i < 3; ++i) {
    
    
	    t[i].join(); 
	  }
} 

  该示例创建三个线程,每个线程写一个随机数到标准输出流。 random_number_generator() 函数将会利用在C++标准里定义的 std::rand() 函数创建一个随机数。 但是用于 std::rand() 的随机数产生器必须先用 std::srand() 正确地初始化。 如果没做,程序始终打印同一个随机数。

  随机数产生器,通过 std::time() 返回当前时间, 在 init_number_generator() 函数里完成初始化。 由于这个值每次都不同,可以保证产生器总是用不同的值初始化,从而产生不同的随机数。 因为产生器只要初始化一次, init_number_generator() 用了一个静态变量 done 作为条件量。

  如果程序运行了多次,写入的三分之二的随机数显然就会相同。 事实上这个程序有个缺陷:std::rand() 所用的产生器必须被各个线程初始化。 因此 init_number_generator() 的实现实际上是不对的,因为它只调用了一次 std::srand() 。使用TLS,这一缺陷可以得到纠正。

#include <boost/thread.hpp> 
#include <iostream> 
#include <cstdlib> 
#include <ctime> 

void init_number_generator() 
{
    
     
	  static boost::thread_specific_ptr<bool> tls; 
	  if (!tls.get()) {
    
    
	    tls.reset(new bool(false)); 
	  }
	  if (!*tls) 
	  {
    
     
	    *tls = true; 
	    std::srand(static_cast<unsigned int>(std::time(0))); 
	  } 
} 

boost::mutex mutex; 

void random_number_generator() 
{
    
     
	  init_number_generator(); 
	  int i = std::rand(); 
	  boost::lock_guard<boost::mutex> lock(mutex); 
	  std::cout << i << std::endl; 
} 

int main() 
{
    
     
	  boost::thread t[3]; 
	
	  for (int i = 0; i < 3; ++i) {
    
    
	    t[i] = boost::thread(random_number_generator); 
	  }
	
	  for (int i = 0; i < 3; ++i) {
    
    
	    t[i].join(); 
	  }
} 

  用一个TLS变量 tls 代替静态变量 done,是基于用 bool 类型实例化的 boost::thread_specific_ptr 。 原则上, tls 工作起来就像 done :它可以作为一个条件指明随机数发生器是否被初始化。 但是关键的区别,就是 tls 存储的值只对相应的线程可见和可用。

  一旦一个 boost::thread_specific_ptr 型的变量被创建,它可以相应地设置。 不过,它期望得到一个 bool 型变量的地址,而非它本身。使用 reset() 方法,可以把它的地址保存到 tls 里面。 在给出的例子中,会动态地分配一个 bool 型的变量,由 new 返回它的地址,并保存到 tls 里。 为了避免每次调用 init_number_generator() 都设置 tls ,它会通过 get() 函数检查是否已经保存了一个地址。

  由于 boost::thread_specific_ptr 保存了一个地址,它的行为就像一个普通的指针。 因此,operator*() 和 operator->() 都被被重载以方便使用。 这个例子用 *tls 检查这个条件当前是 true 还是 false。 再根据当前的条件,随机数生成器决定是否初始化。

  正如所见, boost::thread_specific_ptr 允许为当前进程保存一个对象的地址,然后只允许当前进程获得这个地址。 然而,当一个线程已经成功保存这个地址,其他的线程就会可能就失败。

  如果程序正在执行时,它可能会令人感到奇怪:尽管有了TLS的变量,生成的随机数仍然相等。 这是因为,三个线程在同一时间被创建,从而造成随机数生成器在同一时间初始化。 如果该程序执行了几次,随机数就会改变,这就表明生成器初始化正确了。

笔记跟新记录

时间 内容
2020-12-14 创建笔记

猜你喜欢

转载自blog.csdn.net/Aliven888/article/details/111172194