Boost 库中的实用工具类使用入门(1)
作者:转载自:更新时间:2009-8-2

实用工具类(utility classes)在几乎任何适当规模的 C++ 项目中都是必需的,但是不存在满足此需求的标准方法。通常,团队根据他们的需求编写实用工具类代码,但是由于缺少重要的接口信息,使得相同组织中的其他项目团队无法重用那些类。建议的标准模板库(Standard Template Library,STL)只具有诸如 hash、stack 和 vector 等少数基本类,因此无法有效地用于取代遗留实用工具库。

本文将介绍几个 Boost 实用工具类,包括 tuple、static_assert、pool、random 和 program_options。您需要对标准 STL 具备一定的了解才能充分理解本文的内容。本文中的所有代码都已使用 Boost 1.35 来进行了测试并使用 gcc-3.4.4 来进行了编译。

boost::tuple 类

有时,您希望 C++ 函数返回多个不相关的值。在推出 STL 之前,实现此目的的方法是创建所有不相关变量的结构,并以指针或引用的形式返回它们或作为参数传递给函数——但是任一种方法都不是表达程序员意图的方法。 STL 引入了 pair,可将其用于聚合不相关的数据部分,但它一次只能处理两个数据对象。为了使用 int、char 和 float 的元组(tuple ),您可以按如下方式返回 pair:

make_pair > (3, make_pair ('a', 0.9));
   


随着您添加更多的元素,创建元组结构将变得越来越困难。Boost tuple 类型派上了用场。要使用 boost::tuple,您必须包括头文件 tuple.hpp。要执行元组比较和元组 I/O,您需要分别包括 tuple_comparison.hpp 和 tuple_io.hpp。

第一个使用元组的程序

清单 1 使用 int、char 和 float 的元组并打印内容。


清单 1. 创建 Boost 元组并打印内容
               
#include
#include
#include
#include
using namespace boost;

int main ( )
  {
  tuple t(2, 'a', 0.9);
  std::cout << t << std::endl;
  return 0;
  }
    


此代码的输出为 (2 a 0.9)。请注意,<< 运算符重载 std::ostream,以便通过转储每个单独的 tuple 元素来输出元组。

与元组相关的重要事实

在使用元组时,务必牢记以下事实:

能够形成元组的元素数量目前仅限于 10 个。
元组可以包含用户定义的类类型,但是您必须负责确保那些类已经定义了正确的构造函数和拷贝构造函数 (copy constructor)。清单 2 显示了产生编译时错误的代码部分,因为该拷贝构造函数是私有的。

清单 2. 用于元组的类必须具有正确的拷贝构造函数
                       
#include
#include
#include
#include
using namespace std;

class X
  {
  int x;
  X(const X& u) { x = u.x; }
  public:
    X(int y=5) : x(y) { }
  };

int main ( )
  {
  boost::tuple t(3, X(2));
  return 0;
  }
    


与 STL 提供的 make_pair 函数非常类似,Boost 提供了 make_tuple 例程。要从函数返回元组,您必须调用 make_tuple。可以创建具有临时元素的元组;清单 3 的输出为 (4 0)。

清单 3.使用 make_tuple 来从函数返回元组
                       
#include
#include
#include
#include
using namespace std;

boost::tuple
divide_and_modulo(int a, int b)
  {
  return boost::make_tuple (a/b, a%b);
  }

int main ( )
  {
  boost::tuple t = divide_and_modulo(8, 2);
  cout << t << endl;
  return 0;
  }
        


要访问元组的各个元素,您可以使用 get 例程。此例程具有两种变体,如清单 4 所示。请注意,还可以使用 get 例程来设置元组的各个元素,虽然有些编译器可能不支持此功能。

清单 4. 使用 boost::get 例程
                       
#include
#include
#include
#include
using namespace std;

boost::tuple
divide_and_modulo(int a, int b)
  {
  return boost::make_tuple (a/b, a%b);
  }

int main ( )
  {
  boost::tuple t = divide_and_modulo(8, 2);
  cout << t.get<0>() << endl; // prints 4
  cout << boost::get<1>(t) << endl; // prints 0

  boost::get<0>(t) = 9; // resets element 0 of t to 9
  ++boost::get<0>(t);   // increments element 0 of t
  cout << t.get<1>() << endl; // prints 10
  return 0;
  }


可以使用 const 限定符来声明元组,在这种情况下,用于访问特定元素的 get 调用将返回对 const 的引用。不能对以这种方式访问的元素进行赋值(请参见清单 5)。

清单 5. 使用 const 限定符来声明的元组不可修改
                       
#include
#include
#include
#include
using namespace std;

int main ( )
  {
  const boost::tuple t(8, "Hello World!");
  t.get<1>()[0] = "Y"; // error!
  boost::get<0>(t) = 9; // error!
  return 0;
  }


可以使用关系运算符 ==、!=、<、>、<= 和 >= 对相同长度的元组进行比较。比较不同长度的元组会产生编译时错误。这些运算符的工作原理是从左到右地比较两个参与元组的每个单独的元素(请参见清单 6)。

清单 6. 关系运算符与元组
                       
#include
#include
#include
#include
#include
using namespace std;

int main ( )
  {
  boost::tuple t(8, string("Hello World!"));
  boost::tuple t2(8, string("Hello World!"));
  cout << (t == t2) << endl;

  boost::tuple r(9, string("Hello World!"));
  boost::tuple r2(8, string("Hello World!"));
  cout << (r > r2) << endl;

  boost::tuple q(string("AA"), string("BB"));
  boost::tuple q2(string("AA"), string("CC"));
  cout << (q < q2) << endl;

  return 0;
  }

清单 6 的输出为 1 1 1。请注意,如果您不是使用 string 或 int,而是使用没有定义 ==、!= 等运算符的用户定义的随机类,则会产生编译错误。


 


 回页首
 

 

Boost 静态断言

断言是 C/C++ 中的防错性程序设计的一部分。最常见的用法如下:

assert();
      


assert 例程仅在调试模式下有效。在发布模式下,通常使用预处理器宏 ¨CDNDEBUG 来编译代码,其效果相当于 assert 不存在。静态断言建立在这个基本概念之上,只不过静态断言仅在编译时有效。此外,静态断言不生成任何代码。

例如,假设您在一个整型变量中执行某个位操作,并预期其大小为 4:这并非在所有操作系统平台上都是如此(请参见清单 7)。


清单 7. 使用 Boost 静态断言来验证变量的大小
               
#include
int main ( )
  {
  BOOST_STATIC_ASSERT(sizeof(int) == 4);
   // … other code goes here
  return 0;
  }
    


要使用 BOOST_STATIC_ASSERT 宏,您必须包括 static_assert.hpp 头文件。不需要诸如 DNDEBUG 等特定于编译器的选项,并且您不需要向链接器提供库——单凭该头文件就足够了。

如果断言有效,则代码将顺利编译。但是如果该假设无效,在某些 64 位平台上就可能是如此,则编译器将生成错误消息并停止。使用 g++-3.4.4 进行编译时的典型消息如下:

assert.cc: In function `int main()':
assert.cc:8: error: incomplete type `boost::STATIC_ASSERTION_FAILURE< false>'
    used in nested name specifier
        


这肯定不是最详细的错误消息,但是它指出了具有错误假设的函数和确切行号。

下面是一些典型的现实情景,您应该在其中考虑使用静态断言:

静态声明的数组的边界检查
验证原始和用户定义的变量的大小
允许模板类或函数仅使用某些数据类型来进行实例化
Boost 静态断言的行为

您可以在类、函数或命名空间范围中使用 Boost 静态断言;还可以与模板一起使用它们。清单 8 中的示例阐明了概念。


清单 8. 使用 Boost 静态断言来限制类实例化
               
#include
#include
using namespace std;
using namespace boost;

template
class A
  {
  private:
    T x, y;
    BOOST_STATIC_ASSERT(numeric_limits::is_signed);
  public:
    A(T x1, T y1) : x(x1), y(y1) { }
  };

int main ( )
  {
  A a(2, 1);
  return 0;
  }
    


在清单 8 中,仅当 T 有符号时,模板类 A 才能进行实例化。类 numeric_limits 是标准命名空间的一部分;它检查基本类型在给定操作系统平台上的属性。在无符号(unsigned )的 long 类型的情况下,专用变体 numeric_limits 的 is_signed 标志为 false。当您在类范围中使用 BOOST_STATIC_ASSERT 时,它是私有的、受保护的还是公开的并不重要。

清单 9 将 BOOST_STATIC_ASSERT 与函数结合在一起使用。该代码确保在函数 f1 中处理的类型只能是 A 类型或其派生类型。通过使用 Boost 的静态断言宏和 is_convertible 例程(在 boost/type_traits/is_convertible.hpp 中定义),此代码确保不希望的类型不会最终调用此例程。


清单 9. 将函数限制为仅处理特定的数据类型
               
#include
#include
#include
using namespace std;
using namespace boost;

struct A
  {
  int a;
  float b;
  };

struct B : public A
  {
  };

template
int f1 (T y)
  {
  BOOST_STATIC_ASSERT((is_convertible::&#118alue));
  return 0;
  }

int main ( )
  {
  f1 (new B);
  return 0;
  }
  

 

 


 回页首
 

 

使用 Boost 库生成随机数

随机数生成用于各种各样的计算机应用,例如安全和游戏。UNIX 系统一般附带了随机数生成例程 rand 和 srand。通常,srand 使用新的种子值来初始化 rand(请参见清单 10)。


清单 10. 用于在传统 UNIX 中生成随机数的代码
               
#include
#include

int main ( )
  {
  srand(time(NULL)); // this introduces randomness
  for (int i=0; i<10; i++)
    printf("%d\n", rand());
  return 0;
  }
            


rand 例程返回一个介于 0 和 stdlib.h 中定义的 RAND_MAX 之间的数字。要了解 srand 所做的工作,可以在将 srand 例程注释掉的情况下编译清单 11。当您这样做时,您将观察到 rand 并不真正是随机的——可执行代码每次打印同一组值。为了在代码中引入随机性,您可以使用 srand,此例程使用种子值来初始化 rand。由于每次调用程序时的时间值是不同的,因此对于不同的调用,代码打印的值不同。

 传统随机数生成器的问题
传统随机数生成器具有几个问题。它们无法生成在用户指定范围内均匀分布的随机数,或者符合高斯或二项式分布的随机数。本文仅在可将随机数映射到某个范围内的上下文中讨论随机数。 
 
使用 Boost 随机数生成器

Boost 随机数生成器位于 boost/random 文件夹中。此外,为方便起见,boost/ 目录中的 random.hpp 头文件包括了 boost/random 文件夹中的所有其他头文件。

Boost 随机接口划分为两个部分:随机数生成器和随机数必须位于其中的分布。本文讨论 uniform_int 和 uniform_real random-number 分布以及 mt19937 随机数生成器。清单 11 使用了 uniform_int 和 uniform_real 分布。


清单 11. 将 variate_generator 与 mt19937 引擎和 uniform_int 分布一起使用
               
#include
#include
using namespace std;
using namespace boost;

int main ( )
  {
  uniform_int<> distribution(1, 100) ;
  mt19937 engine ;
  variate_generator > myrandom (engine, distribution);

  for (int i=0; i<100; ++i)
    cout << myrandom() << endl;

  return 0;
  }
 


此代码生成介于 1 和 100 之间(包括 1 和 100)的随机数;用于实现随机化的基础引擎是 mt19937。variate_generator 为您组合了该引擎和分布。

清单 12 使用了另一个引擎: kreutzer1986.


清单 12:组合 uniform_real 分布和 kreutzer1986 引擎
               
#include
#include
using namespace std;
using namespace boost;

int main ( )
  {
  uniform_real<> distribution(1, 2) ;
  kreutzer1986 engine ;
  variate_generator > myrandom (engine, distribution);

  for (int i=0; i<100; ++i)
    cout << myrandom() << endl;

  return 0;
  }