写简历时总写“精通C++”,可慢慢的发现自己懂得真的只是皮毛……=。=(包罗万象的boost也是到今年才开始了解,随python的深入也开始看看boost)。本文转载自IBM DeveloperWorks,这里经常有比较好的技术介绍文章。http://www.ibm.com/developerworks/cn/aix/library/au-util_boost_lib/(原文出处)
实用工具类(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<int, pair<char, float> > (3, make_pair<char, float> ('a', 0.9));
|
随着您添加更多的元素,创建元组结构将变得越来越困难。Boost tuple
类型派上了用场。要使用 boost::tuple
,您必须包括头文件 tuple.hpp。要执行元组比较和元组 I/O,您需要分别包括 tuple_comparison.hpp 和 tuple_io.hpp。
第一个使用元组的程序
清单 1 使用 int
、char
和 float
的元组并打印内容。
#include <iostream>
#include <tuple.hpp>
#include <tuple_comparison.hpp>
#include <tuple_io.hpp>
using namespace boost;
int main ( )
{
tuple<int, char, float> t(2, 'a', 0.9);
std::cout << t << std::endl;
return 0;
}
|
此代码的输出为 (2 a 0.9)
。请注意,<<
运算符重载 std::ostream
,以便通过转储每个单独的 tuple
元素来输出元组。
与元组相关的重要事实
在使用元组时,务必牢记以下事实:
#include <tuple.hpp>
#include <tuple_comparison.hpp>
#include <tuple_io.hpp>
#include <iostream>
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<int, X> t(3, X(2));
return 0;
}
|
make_pair
函数非常类似,Boost 提供了 make_tuple
例程。要从函数返回元组,您必须调用 make_tuple
。可以创建具有临时元素的元组;清单 3 的输出为 (4 0)
。
#include <tuple.hpp>
#include <tuple_comparison.hpp>
#include <tuple_io.hpp>
#include <iostream>
using namespace std;
boost::tuple<int, int>
divide_and_modulo(int a, int b)
{
return boost::make_tuple<int, int> (a/b, a%b);
}
int main ( )
{
boost::tuple<int, int> t = divide_and_modulo(8, 2);
cout << t << endl;
return 0;
}
|
get
例程。此例程具有两种变体,如清单 4 所示。请注意,还可以使用 get
例程来设置元组的各个元素,虽然有些编译器可能不支持此功能。
#include <tuple.hpp>
#include <tuple_comparison.hpp>
#include <tuple_io.hpp>
#include <iostream>
using namespace std;
boost::tuple<int, int>
divide_and_modulo(int a, int b)
{
return boost::make_tuple<int, int> (a/b, a%b);
}
int main ( )
{
boost::tuple<int, int> 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)。
#include <tuple.hpp>
#include <tuple_comparison.hpp>
#include <tuple_io.hpp>
#include <iostream>
using namespace std;
int main ( )
{
const boost::tuple<int, char*> t(8, "Hello World!");
t.get<1>()[0] = "Y"; // error!
boost::get<0>(t) = 9; // error!
return 0;
} |
==
、!=
、<
、>
、<=
和 >=
对相同长度的元组进行比较。比较不同长度的元组会产生编译时错误。这些运算符的工作原理是从左到右地比较两个参与元组的每个单独的元素(请参见清单 6)。
#include <tuple.hpp>
#include <tuple_comparison.hpp>
#include <tuple_io.hpp>
#include <iostream>
#include <string>
using namespace std;
int main ( )
{
boost::tuple<int, string> t(8, string("Hello World!"));
boost::tuple<int, string> t2(8, string("Hello World!"));
cout << (t == t2) << endl;
boost::tuple<int, string> r(9, string("Hello World!"));
boost::tuple<int, string> r2(8, string("Hello World!"));
cout << (r > r2) << endl;
boost::tuple<string, string> q(string("AA"), string("BB"));
boost::tuple<string, string> q2(string("AA"), string("CC"));
cout << (q < q2) << endl;
return 0;
} |
1 1 1
。请注意,如果您不是使用 string
或 int
,而是使用没有定义 ==
、!=
等运算符的用户定义的随机类,则会产生编译错误。回页首
Boost静态断言
断言是 C/C++ 中的防错性程序设计的一部分。最常见的用法如下:
assert(<some expression you expect to be true at this point in code>);
|
assert
例程仅在调试模式下有效。在发布模式下,通常使用预处理器宏 ¨CDNDEBUG
来编译代码,其效果相当于 assert 不存在。静态断言建立在这个基本概念之上,只不过静态断言仅在编译时有效。此外,静态断言不生成任何代码。
例如,假设您在一个整型变量中执行某个位操作,并预期其大小为 4:这并非在所有操作系统平台上都是如此(请参见清单 7)。
#include <boost/static_assert.hpp>
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:使用静态断言来限制实例化
#include <iostream>
#include <static_assert.hpp>
using namespace std;
using namespace boost;
template<class T>
class A
{
private:
T x, y;
BOOST_STATIC_ASSERT(numeric_limits<T>::is_signed);
public:
A(T x1, T y1) : x(x1), y(y1) { }
};
int main ( )
{
A<unsigned long> a(2, 1);
return 0;
}
|
在清单 8 中,仅当 T
有符号时,模板类 A
才能进行实例化。类 numeric_limits
是标准命名空间的一部分;它检查基本类型在给定操作系统平台上的属性。在无符号(unsigned )的 long
类型的情况下,专用变体 numeric_limits<unsigned int>
的 is_signed
标志为 false。当您在类范围中使用 BOOST_STATIC_ASSERT
时,它是私有的、受保护的还是公开的并不重要。
清单 9 将 BOOST_STATIC_ASSERT
与函数结合在一起使用。该代码确保在函数 f1
中处理的类型只能是 A
类型或其派生类型。通过使用 Boost 的静态断言宏和 is_convertible
例程(在 boost/type_traits/is_convertible.hpp 中定义),此代码确保不希望的类型不会最终调用此例程。
#include <iostream>
#include <static_assert.hpp>
#include <boost/type_traits/is_convertible.hpp>
using namespace std;
using namespace boost;
struct A
{
int a;
float b;
};
struct B : public A
{
};
template <typename T>
int f1 (T y)
{
BOOST_STATIC_ASSERT((is_convertible<T, A*>::value));
return 0;
}
int main ( )
{
f1<B*> (new B);
return 0;
}
|
回页首
使用Boost库生成随机数
随机数生成用于各种各样的计算机应用,例如安全和游戏。UNIX 系统一般附带了随机数生成例程 rand
和 srand
。通常,srand
使用新的种子值来初始化 rand
(请参见清单 10)。
#include <stdlib.h>
#include <stdio.h>
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
分布。
#include <iostream>
#include <boost/random.hpp>
using namespace std;
using namespace boost;
int main ( )
{
uniform_int<> distribution(1, 100) ;
mt19937 engine ;
variate_generator<mt19937, uniform_int<> > myrandom (engine, distribution);
for (int i=0; i<100; ++i)
cout << myrandom() << endl;
return 0;
}
|
此代码生成介于 1 和 100 之间(包括 1 和 100)的随机数;用于实现随机化的基础引擎是 mt19937
。variate_generator
为您组合了该引擎和分布。
清单 12 使用了另一个引擎: kreutzer1986
.
#include <iostream>
#include <boost/random.hpp>
using namespace std;
using namespace boost;
int main ( )
{
uniform_real<> distribution(1, 2) ;
kreutzer1986 engine ;
variate_generator<kreutzer1986, uniform_real<> > myrandom (engine, distribution);
for (int i=0; i<100; ++i)
cout << myrandom() << endl;
return 0;
}
|
除了 uniform_int
和 uniform_real
分布以外,Boost 还提供了几个其他分布,包括二项式、泊松和正态分布。
回页首
boost:pool库概述
Boost pool
库引入了可用于实现快速内存分配的工具。正确的内存块对齐可以得到保证。
根据 Boost 文档所述,当您分配和释放许多小型对象时,建议使用池。使用池的另一个不太明显的优点在于,作为程序员,您不必担心内存泄露:内存由 Boost 库在内部自动进行管理。要使用 pool
库,您不必在链接时提供特定的库——单凭头文件就足以完成链接了。
有多个接口对 pool
库可用:
malloc
进行工作的普通接口。要使用此接口,需要包括 boost/pool 文件夹中的 pool.hpp 头文件。pool
和 object_pool
接口。请注意以下几点:
pool
接口需要知道每个单独的元素而不是类型的大小,因为它是一个 malloc
风格的分配程序,不会调用构造函数。pool
接口中的 malloc
例程返回 void*
。object-pool
接口需要类型信息,因为要调用构造函数。object-pool
接口中的 malloc/construct
例程返回指向类型的指针。malloc
例程不调用构造函数,但是 construct
要调用构造函数。pool
接口或 object-pool
接口来创建的元素的范围与从中创建它们的池的范围相同。purge_memory
方法。该方法释放您先前创建的内存块,并使得从分配程序例程返回的所有指针失效。pool
接口中的 free
例程。例如,如果 t
是使用 pool
接口来创建的池,并且 m
是从 t
分配的指针,则 t.free(m)
将把内存返回给 t
(将其添加到 t
的空闲内存列表)。
#include <iostream>
#include <boost/pool/pool.hpp>
#include <boost/pool/object_pool.hpp>
using namespace std;
using namespace boost;
class A
{
public: A( ) { cout << "Declaring A\n"; }
~A( ) { cout << "Deleting A\n"; }
};
int main ( )
{
cout << "Init pool...\n";
pool<> p(10 * sizeof(A));
for (int i=0; i<10; ++i)
A* a = (A*) p.malloc(); // Always returns sizeof(A)
p.purge_memory();
cout << "Init object pool...\n";
object_pool<A> q;
for (int i=0; i<10; ++i)
A* a = q.construct(); // Calls A's constructor 10 times
return 0;
}
|
singleton_pool
接口——与 pool
接口几乎相同,但是用作独立池。独立池的底层结构具有为 malloc
、free
等声明的静态成员函数,并且构造函数是私有的。独立池声明中的第一个参数称为标记——它允许存在不同的独立池集(例如,用于 int
的多个池,其中每个池服务于不同的目的)。必须包括 singleton_pool.hpp 头文件才能使用此接口。请参见清单 14。
#include <iostream>
#include <boost/pool/singleton_pool.hpp>
using namespace std;
using namespace boost;
struct intpool { };
struct intpool2 { };
typedef boost::singleton_pool<intpool, sizeof(int)> ipool1;
typedef boost::singleton_pool<intpool2, sizeof(int)> ipool2;
int main ( )
{
cout << "Init singleton pool...\n";
for (int i=0; i<10; ++i) {
int* q1 = (int*) ipool1::malloc();
int* q2 = (int*) ipool2::malloc();
}
ipool1::purge_memory();
ipool2::purge_memory();
return 0;
}
|
pool_alloc
接口——通常与 STL 容器结合在一起使用。请考虑以下代码片段:
#include <boost/pool/pool_alloc.hpp>
std::vector<int, boost::pool_allocator<int> > v;
std::list<double, boost::fast_pool_allocator<double> > L;
|
pool_allocator
和 fast_pool_allocator
。第一个分配程序是通用分配,可以满足针对任何数量的连续内存块的请求。fast_pool_allocator
最适合于一次请求单个(通常较大)块,但是也适用于通用分配,不过具有一些性能缺点。回页首
:boost:program_options简介
命令行处理是另一个难点,开发人员通常不会采用结构化的方式来解决。其结果是从头到尾维护开销。Boost program_options
库提供了简化命令行处理的例程和数据结构。
清单 15 详细描述了 boost::program_options
的使用。这是建议在您的代码中使用的标准模板。
#include <string>
#include <iostream>
#include <boost/program_options.hpp>
using namespace std;
int main (int ac, char* av[])
{
boost::program_options::options_description options("command line options");
options.add_options() ("help", "Use -h or --help to list all arguments")
("file", boost::program_options::value<string>(),
"Provide input file name");
boost::program_options::variables_map vmap;
boost::program_options::store(
boost::program_options::parse_command_line(ac, av, options), vmap);
boost::program_options::notify(vmap);
if (vmap.count("help")) {
cout << options << endl;
}
return 0;
}
|
您必须包括 program_options.hpp 头文件。清单 15 的工作方式如下:
options_description
类声明所有的有效命令行选项。add_options
,您可以注册命令和跟在命令后面的参数类型。在此例中,help
选项不需要任何参数,但是 file
选项需要一个字符串参数。variables_map
类在运行时存储命令行选项及其参数。parse_command_line
例程解析 argc
和 argv
参数。store
和 notify
方法帮助存储 vmap
对象中的数据。help
是否为程序的恰当命令行选项(这是 vmap.count("help")
所做的工作)时,options
对象将被转储到 cout
。这意味着运算符 <<
是为 options_description
类定义的。下面是来自清单 15 的输出:
[user@/home/user1] ./a.out --help
command line options:
--help Use -h or --help to list all arguments
--file arg Provide input file name
|
当您遇到其他选项时,可以采取进一步的操作。例如,下面的代码片段经过了修改,以打印您输入的文件名:
…
if (vmap.count("file")) {
cout << "Setting input file to " << vmap["file"].as<string>() << ".\n";
} else {
cout << "No file specified\n";
}
…
|
请注意,variable_map
类在许多方面与哈希表非常相似。例如,要检索 file 参数,您可以调用 vmap["file"]
。
回页首
:提供多个参数和缩写的命令选项
命令行处理通常同时需要同一个命令选项的短名称和长名称。此外,您通常必须多次使用某个选项,以便收集该选项的所有参数。例如,您可能希望使用 ¨Ch
和 ¨Chelp
来打印可用的命令。清单 16 演示了这些功能。
#include <string>
#include <iostream>
#include <boost/program_options.hpp>
using namespace std;
int main (int ac, char* av[])
{
boost::program_options::options_description options("command line options");
options.add_options() ("help,h", "Use -h or --help to list all arguments")
("file", boost::program_options::value<vector<string> >( ),
"Provide input file name");
boost::program_options::variables_map vmap;
boost::program_options::store(
boost::program_options::parse_command_line(ac, av, options), vmap);
boost::program_options::notify(vmap);
if (vmap.count("help")) {
cout << options << endl;
}
if (vmap.count("file")) {
vector<string> ifiles(vmap["file"].as< vector<string> > ());
vector<string>::iterator vI;
cout << "Number of input files: " << ifiles.size() << endl;
cout << "Input file list: " << endl;
for(vI = ifiles.begin(); vI != ifiles.end(); ++vI)
cout << "\t" << *vI << endl;
} else {
cout << "No file specified\n";
}
return 0;
}
|
在使用 add_options
来添加命令选项时,较长和较短的选项之间使用逗号进行分隔。请注意,较长的选项 (help
) 必须在较短的选项 (h
) 之前,代码才能正常工作。与使用单个字符串不同,file
选项现在是使用一个字符串向量来定义的。如果指定了 ¨Cfile
选项多次,则会将在所有指定中收集到的命令选项参数存储在关联的 vector<string>
中。下面是使用不同的参数来多次指定 ¨Ch
和¨Cfile
所获得的输出:
[user@/home/user1] ./a.out -h
command line options:
-h [ --help ] Use -h or --help to list all arguments
--file arg Provide input file name
No file specified
[user@/home/user1] ./a.out --file abc --file pqr
Number of input files: 2
Input file list:
abc
pqr
|
回页首
解析位置选项
带输入参数但是不带命令行选项来调用某个程序是非常普遍的。您预期参数和命令行选项之间自动存在某种神奇关联。这种行为由boost::program_options
提供支持。
请考虑清单 17。第一个参数转换为 --file=<first parameter>
,第二个参数转换为 --do-file=<second parameter>
。
#include <string>
#include <iostream>
#include <boost/program_options.hpp>
using namespace std;
int main (int ac, char* av[])
{
boost::program_options::options_description options("command line options");
options.add_options() ("help,h", "Use -h or --help to list all arguments")
("file", boost::program_options::value<string>(),
"Provide input file name")
("do-file", boost::program_options::value<string>(),
"Specify commands file");
boost::program_options::variables_map vmap;
boost::program_options::positional_options_description poptd;
poptd.add("file", 1);
poptd.add("do-file", 2);
boost::program_options::store(
boost::program_options::command_line_parser(ac, av).
options(options).positional(poptd).run(), vmap);
boost::program_options::notify(vmap);
if (vmap.count("file")) {
cout << "file: " << vmap["file"].as<string> ( ) << endl;
}
if (vmap.count("do-file")) {
cout << "do-file: " << vmap["do-file"].as<string> ( ) << endl;
}
return 0;
}
|
下面是输出内容:
[user@/home/user1] ./a.out file1 dofile1
file: file1
do-file: dofile1
|
清单 15 中使用的某些 API 在清单 17 中已发生更改。清单 17 引入了新的类 positional_options_description
。该类的 add
方法(add("command option", N)
)将位置 N 处的输入参数与命令行选项 "command option"
相关联。因此,./a.out file1
在内部解析为 ./a.out ¨Cfile=file1
。另一个区别在于调用 program_options::store
方法的方式。与使用 parse_command_line
例程不同,Boost 库要求您将 command_line_parser
例程与 store
方法结合在一起使用。
请注意,仍然可以使用 ¨Cfile
和 ¨Cdo-file
选项来调用该程序。最后,若要将所有的输入参数与同一个命令行选项相关联,您需要使用值 -1 将该命令行选项添加到 positional_options_description
对象。下面是代码:
…
boost::program_options::positional_options_description poptd;
poptd.add("file", -1);
...
|