Boost库中的实用东西类利用入门
副标题#e#
简介:实用东西类开拓和维护会淹灭措施员的时间。Boost 通过提供几个高质量的库,从而简化了此 进程,您可以将这些库轻松集成到现有的代码库中。本文简朴概述一些较风行的 Boost 实用东西类,并 辅佐您相识如何将它们投入利用。
实用东西类(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 的元组并打印内容。
清单 1. 建设 Boost 元组并打印内容
#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 元素来输出元组。
#p#副标题#e#
与元组相关的重要事实
在利用元组时,务必紧记以下事实:
可以或许形成元组的元素数量今朝仅限于 10 个。
元组可以包括用户界说的类范例,可是您必需认真确保那些类已经界说了正确的结构函数和拷贝结构 函数 (copy constructor)。清单 2 显示了发生编译时错误的代码部门,因为该拷贝结构函数是私有的。
清单 2. 用于元组的类必需具有正确的拷贝结构函数
#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;
}
与 STL 提供的 make_pair 函数很是雷同,Boost 提供了 make_tuple 例程。要从函数返回元组,您 必需挪用 make_tuple。可以建设具有姑且元素的元组;清单 3 的输出为 (4 0)。
清单 3.利用 make_tuple 来从函数返回元组
#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 例程来配置元组的各个元素,固然有些编译器大概不支持此成果。
清单 4. 利用 boost::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;
}
#p#分页标题#e#
可以利用 const 限定符来声明元组,在这种环境下,用于会见特定元素的 get 挪用将返回对 const 的引用。不能对以这种方法会见的元素举办赋值(请拜见清单 5)。
清单 5. 利用 const 限定符来声明的元组不行修改
#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)。
清单 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;
}
清单 6 的输出为 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)。
清单 7. 利用 Boost 静态断言来验证变量的巨细
#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. 利用 Boost 静态断言来限制类实例化
#p#分页标题#e#
#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 中界说),此代码确保不但愿的范例不会最终挪用此例程。
清单 9. 将函数限制为仅处理惩罚特定的数据范例
#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)。
清单 10. 用于在传统 UNIX 中生成随机数的代码
#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 漫衍。
清单 11. 将 variate_generator 与 mt19937 引擎和 uniform_int 漫衍一起利用
#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.
清单 12:组合 uniform_real 漫衍和 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 库引入了可用于实现快速内存分派的东西。正确的内存块对齐可以获得担保。
#p#分页标题#e#
按照 Boost 文档所述,当您分派和释放很多小型工具时,发起利用池。利用池的另一个不太明明的优 点在于,作为措施员,您不必担忧内存泄露:内存由 Boost 库在内部自动举办打点。要利用 pool 库, 您不必在链接时提供特定的库——单凭头文件就足以完成链接了。
有多个接口对 pool 库可用:
池接口——替代 malloc 举办事情的普通接口。要利用此接口,需要包罗 boost/pool 文件夹中的 pool.hpp 头文件。
工具池接口——有工具意识的接口,在工具建设和删除进程中别离相应地挪用结构函数和析构函数。 还可以利用此接口建设普通工具,而不挪用它们的结构函数。接口界说是在位于 boost/pool 目次中的 object_pool.hpp 头文件中提供的。清单 13 引入了 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 的空闲内存列表)。
清单 13. pool 和 object_pool 接口
#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 An"; }
~A( ) { cout << "Deleting An"; }
};
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。
清单 14. singleton_pool 接口
#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 的利用。这是发起在您的代码中利用的尺度模板。
清单 15. 利用 boost::program_options
#p#分页标题#e#
#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 类在运行时存储呼吁行选项及其参数。
Boost 的 parse_command_line 例程理会 argc 和 argv 参数。store 和 notify 要领辅佐存储 vmap 工具中的数据。
当您查抄 help 是否为措施的得当呼吁行选项(这是 vmap.count("help") 所做的事情)时,options 工具将被转储到 cout。这意味着运算符 << 是为 options_description 类界说的。
下面是来自清单 15 的输出:
[[email protected]/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 specifiedn";
}
…
请留意,variable_map 类在很多方面与哈希表很是相似。譬喻,要检索 file 参数,您可以挪用 vmap["file"]。
提供多个参数和缩写的呼吁选项
呼吁行处理惩罚凡是同时需要同一个呼吁选项的短名称和长名称。另外,您凡是必需多次利用某个选项, 以便收集该选项的所有参数。譬喻,您大概但愿利用 ¨Ch 和 ¨Chelp 来打印可用的呼吁。清单 16 演 示了这些成果。
清单 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 specifiedn";
}
return 0;
}
#p#分页标题#e#
在利用 add_options 来添加呼吁选项时,较长和较短的选项之间利用逗号举办脱离。请留意,较长的 选项 (help) 必需在较短的选项 (h) 之前,代码才气正常事情。与利用单个字符串差异,file 选项此刻 是利用一个字符串向量来界说的。假如指定了 ¨Cfile 选项多次,则会将在所有指定中收集到的呼吁选 项参数存储在关联的 vector<string> 中。下面是利用差异的参数来多次指定 ¨Ch 和 ¨Cfile 所得到的输出:
[[email protected]/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
[[email protected]/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>。
清单 17. 将位置参数与呼吁行选项相关联
#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;
}
下面是输出内容:
[[email protected]/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);
...
竣事语
本文提供了 Boost 库中较为有用的一些实用东西类的概述。只要利用恰当,它们可觉得您省去大量的 代码维护开销。鉴于所接头的观念的范畴和广度,论及每个实用东西类的每个成果是不切实际的。参考资 料部门提供了进一步的阅读参考。