Boost入门向导
boost是一套开源的、高度可移植的C++模板库。它由C++标准委员发起,且里面很多组件有望成为下一代的C++标准库,其地位将会与STL一样。
boost按功能分为:字符串、容器、算法、迭代器、数据结构、内存管理、多线程、IO等。其中字符串组中的正规表达式可以与POSIX API和Perl语言处理正则表达式的功能相媲美;多线程组件不比java的多线程难用;内存管理组件如果能合理使用可以杜绝内存泄露,而其效率更是与垃圾收集机制不可同日而语。
boost的接口(或concept)完全与STL兼容,它在实现上使用了很多STL功能,它本身的组件也可以作为STL容器及算法的元素,所以有使用上,感觉不出是两个类库,我们可以当成是一个类库来使用就行了。只是boost库是在boost::名字空间里。
而我个人最喜欢的一点是,boost的源码可读性非常高,非不得已它很少用宏,且不会象某些STL的实现一样,到处都是下划线开头的变量。
当boost成为标准库后,以下步骤就可以省略了,现在要用boost,还得亲自动手。
下载之后,X/boost_1_31_0/ index.htm是一个本地的文档目录。
大多数boost组件只要直接包含相应头文件就可以使用
[1]。但如果你用到date_time、filesystem、graph、python、regex、signals、test、thread
等组件,则要要编译成相应的静态或动态库。
新版本的boost在所有平台下统一使用jam完成编译与部署,并且boost还特别特化了一个jam版本bjam。这里以1.31.0版本为例,编译所有的boost库:
执行boost源码发布包中的X/boost_1_31_0/tools/build/jam_src下的build.bat,build.bat将自动在系统中选择一个编译器,并在bin.xxxx目录下生成jam,bjam等工具。
生成bjam后,配置好命令行编译环境。并用bjam解释(boostsrc)/下的jam文件即可编译并部署。其参数繁多,这里不一一列出,见X/boost_1_31_0/boost_1_31_0/more/getting_started.html #Build_Install。
String and text processing:
提供了字符串的操作功能,大概分为五个组件。但要注意的是,某些组件效率上可能会有问题,比如字符串与基本类型的相互转换、格式化等,它们的实现是先把输入参数转化为STL的字符流,然后通过字符流转换为用户想要的类型输出(从这一点也可以看出,可以相互转换的类型只限于字符流可接受的那几类,字符流使用与可接受类型与io流基本一样)。
组件 |
描述 |
lexical_cast |
字符串与其它类型的相互转换 |
format |
格式化字符 |
regex |
正则表达式 |
spirit |
字符串分析框架。用inline的C++写成的EBNF语法。 |
tokenizer |
按位移或字符提取字符串 |
很多时候,我们想要把字符串与其它类型相互转换,而使用标签的C库,比如atoi,itoa等有时会很麻烦,而且难以记住所有这些函数,使用lexical_cast做这种事情则非常之简单而安全。例子如下:
#include <boost/lexical_cast.hpp>
void test_lexical_cast()
{
std::string strNember = boost::lexical_cast<std::string>(123);
int a = boost::lexical_cast<int>("123");
double b = boost::lexical_cast<double>("123.456");
}
要注意的是,test_lexical_cast里的最后两句在VC6.0下不能通过编译。
format的使用与一般的C格式化函数要灵活的多,而且它提供了多种语言风格的格式化。format其实是一个仿函数,查看源代码有以下的定义:
typedef basic_format<char > format;
format要真正格式化的参数是通过operator%来传递给函数对象的。下面是一个简单的例子:
#include <boost/format.hpp>
std::cout <<
boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50;
format生成的对象可以直接入到输出流,要生成一个字符串,我们可以这样:
std::string str = boost::lexical_cast<std::string>(
boost::format("%1% %2% %3% %2% %1% /n") % "a" % "b" % "c"
);
这里生成的字符串是:"abcba /n"(%1%对应的参数是字符a,%2%对应的参数是字符b,%3%对应的参数是字符c)。
要分析一个字符串是否符合某种语法规则是一件很烦人而且容易出错的事,boost::regex组件实现了这种分析功能,我们就不用自己去分析了。
语法分析通常使用正则表达式,regex正是通过接受一个正则表达式字符串作为参数,来分析给定的字符串是否符合某种语法规则的。
下面这个例子字符串"abcd"是否符合表达式 /d{4}[- ]){3}/d{4}:
#include<boost/regex.hpp>
std::string s("abcd");
static const boost::regex e("(//d{4}[- ]){3}//d{4}");
if(boost::regex_match(s, e))
std::cout << "match /n";
else
std::cout << "not match /n";
我们还可以分析一篇文档,提取只符合表达式的内容,比如,有一篇xml文档,超级链接的地址是放在属性href中,我想要提取所有超链接的地址可以这样写表达式:
boost::regex expression("//s+href//s*=//s*/"([^/"]*)/"");
完整的代码如下:
void test_regex_split()
{
using namespace boost;
regex expression("//s+href//s*=//s*/"([^/"]*)/"");
// 假如文档的内容如下:
std::string s = "<a href=/"index.html/"><img src=/"logo.gif/"></a>";
std::vector<std::string> result; // 用于保存结果
// 把字符串s按表达式expression分割,并把结果放到result中
regex_split(std::back_inserter(result), s, expression);
for (std::vector<std::string>::iterator it = result.begin(); it != result.end(); ++it)
std::cout << *it;
std::cout << std::endl;
}
注意,要用regex是需要编译成相应的库的。
tokenizer组件提供了一种非常弹性且容易使用的方法来分割字符串。它的使用比较简单,下面两个例子,例子一是把"hello, word!"分成7+5两个字符,例子二是把字符串按分隔符符分隔开:
#include <boost/tokenizer.hpp>
void test_tokenizer1()
{
std::string s = "hello, word!";
int offsets[] = {7, 5};// 分成两串,一串7个字符,一串5个
boost::offset_separator f(offsets, offsets+2);
typedef boost::tokenizer<boost::offset_separator> SeparatoTokenizer;
SeparatoTokenizer tok(s, f);
for(SeparatoTokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
{
std::cout << "<" << *it << "> " << ‘/t‘;
}
std::cout << std::endl;
}
void test_tokenizer2()
{
std::string str = ";;Hello|world||-foo--bar;yow;baz|";
boost::char_separator<char> sep("-;|"); //分隔符
typedef boost::tokenizer<boost::char_separator<char> > CharTokenizer;
CharTokenizer tokens(str, sep);
for (CharTokenizer::iterator tok_iter = tokens.begin(); tok_iter != tokens.end(); ++tok_iter)
{
std::cout << "<" << *tok_iter << "> " << ‘/t‘;
}
std::cout << std::endl;
}
boost中的容器主要是作为STL容器的一个补充。静态数组、多维数组的使用跟一般的C语法数组差不多,但作为容器,它提供了STL中的很多concept(比如iterater),所以可以使用STL算法来访问它们。dynamic_bitset 是一种动态的biset。property map是把key对象影射到value对象,所有具有下标操作的类型都可以作为它的元素,对property map的需求来源于BGL。
组件 |
描述 |
array |
符合STL容器语意4的静态数组 |
multi_array |
多维数组,而且可以与array适配(前提是有共同的边界) |
dynamic_bitset |
一种可以在运行时改变大小的bitset。 |
property map |
一套可以把key对象影射到value对象的类与全局函数 |
graph |
图容器,即BGL[2] |
STL提供了一套接口(或concept),用于处理不同容器的算法。但普通数组却因为没有相应的接口
[3]而不能很好的配合这些算法的使用。
boost中的array具有静态数组的效率与功能,且提供了STL容器的各种接口。
使用STL,如果要声明一个二元的整数数组,我们可以这样:
std::vector<std::vector<int>>
如果要三维,四维,N维,还使用这种方式?先不说这种代码有多恶心,做起来有多麻烦,只要能完成工作就行了,但很多时候偏偏不能,比如在效率上有要求的时候(想一想vector的实现,这种动态增加如果发生在N维数组上)。而使用普通的数组,又不能很好配合STL算法,这在上面已经提过了。
而boost的multi_array组件提供了标准库的接口,而且功能与效率上与普通数组一样。下面是一个该组件的简单例子:
#include "boost/multi_array.hpp"
void test_array ()
{
// 创建一个 3 x 4 x 2 的3D数组
boost::multi_array<double, 3> A(boost::extents[3][4][2]);
typedef boost::multi_array<double, 3>::index index;
// 给数组的元素赋值
int values = 0;
for(index i = 0; i != 3; ++i)
for(index j = 0; j != 4; ++j)
for(index k = 0; k != 2; ++k)
A[i][j][k] = values++;
// 输出数组的元素值
for(i = 0; i != 3; ++i)
for(index j = 0; j != 4; ++j)
for(index k = 0; k != 2; ++k)
std::cout << A[i][j][k] << "/t";
std::cout << std::endl;
}
boost的dynamic_bitset几乎等价于std::bitest,不同的是,dynamic_bitset的大小是可以在运行时改变的。该组件在使用VC6.0下编译不过。
property map定义了一套接口把key对象影射到相应的value对象,所有具有下标操作的类型(比如指针、数组、std::map等)都可以作为它的元素。对property map的需求最初来源于BGL。
property map的接口包含三个全局函数get(), put(), 和 operator[]。
下面是property map的使用例子,例子中使用到了boost::associative_property_map类,但其实使用其它的具有下标操作的类型也一样可以:
#include <boost/property_map.hpp>
template <typename AddressMap>
void foo(AddressMap address)
{
typedef typename boost::property_traits<AddressMap>::value_type value_type;
typedef typename boost::property_traits<AddressMap>::key_type key_type;
value_type old_address, new_address;
key_type fred = "Fred";
old_address = boost::get(address, fred);
new_address = "384 Fitzpatrick Street";
boost::put(address, fred, new_address);
address["Joe"] = "325 Cushing Avenue";
}
void test_property_map()
{
typedef std::map<std::string, std::string> NameAddrMap;
NameAddrMap name2address;
boost::associative_property_map<NameAddrMap> address_map(name2address);
name2address.insert(std::make_pair(std::string("Fred"),
std::string("710 West 13th Street")));
name2address.insert(std::make_pair(std::string("Joe"),
std::string("710 West 13th Street")));
foo(address_map);
for (NameAddrMap::iterator it = name2address.begin(); it != name2address.end(); ++it)
std::cout << it->first << ": " << it->second << "/n";
}
组件 |
描述 |
any |
可以接受不同的类型的值,当一个容器要接受不同的类型元素时用它会很方便 |
compressed_pair
|
与std::pair相似,但如果其中一个元素为空类时,会比std::pair更节省空间 |
tuple
|
可以定义一个或多个元素的结构,作为函数返回值时会很方便 |
如果说,pair是可以定义有两个元素的结构体,那么tuple是可以定义1到10元素的结构体。tuple的大部分定义是在boost::tuples::名字空间内(除了某些很通用的是在boost::内)
访问tuple元素可以通过两个方式,
t.get<N>()
或者
get<N>(t)
这里,t是一个tuple实例。(第一种方法在VC6.0下编译不过)
它的使用例子如下:
#include "boost/tuple/tuple.hpp"
void test_tuple()
{
// 最多可以10元素
boost::tuples::tuple<char, double, std::string> triples(‘a‘, 2.4, "hello");
std::cout << boost::tuples::get<0>(triples) << ‘/t‘
<< boost::tuples::get<1>(triples) << ‘/t‘
<< boost::tuples::get<2>(triples) << std::endl;
}
Memory中的组件比较通用,pool组件提供的内存分配可以使指针为作一个真正的原生指针,而又不用管理内存。智能指针要比std::auto_prt的好,但并非不可以代替(之前网上有编文章评论没有loki提供智能指针的好用,而且KFC也有相应的组件。)
组件 |
描述 |
pool |
内存池管理 |
smart_ptr |
智能指针,总共提供了6种类型 |
pool是什么?
pool是一套非常高效的内存分配方案。
为什么要使用pool?
使用pool分配内存得到的指针是真正的指针,这意味着,使用者可以对内存有更多的控制(相对于智能指针)。使用pool接口,你可以选择只运行对象的析构函数或只简单地对指向对象的指针回收。pool会保证没有内存泄漏。
什么时候使用pool?
当有大量小对象分配与回收,而又不想去亲自去管理内存时。
总之,当你想要高效率的方式去操纵内存时用它会带来很多好处。
pool 提供了四个比较通常的组件:pool、object_pool、singleton_pool、pool_alloc。
下面给出前两个组件的简单使用样例:
#include <boost/pool/pool.hpp>
void test_pool_BaseType()
{
boost::pool<> p(sizeof(int));
for (int i = 0; i < 5; ++i)
{
int* const t = (int*)p.malloc();
*t = i;
std::cout << *t << ‘/t‘;
}
std::cout << std::endl;
}// on function exit, p is destroyed, and all malloc()‘ed ints are implicitly freed
#include <boost/pool/object_pool.hpp>
class X {};
void test_pool_object()
{
boost::object_pool<X> p;
for (int i = 0; i < 10000; ++i)
{
X* const t = p.construct();
// to do
}
}
使用pool,我们可以不用管内存释放。当然,如果你想自己释放内存,可以使用void destroy(element_type * p)成员函数。这里element_type是传进来的模板参数类型。
object_pool是使用内存池管理内存,如果频繁分配和删除相同类型的对象,object_pool要比直接调用new,delete高出几个数量级。
STL的std::auto被很多人认为是标准库的一个缺陷,其实并不是这样,只是因为std::auto提供的功能不够丰富摆了,它缺少对引用数和数组的支持,并且,std::auto_ptr在被复制的时候会传输所有权。因为缺少引用,所以对象没有共享所有权,结果就不可以跟其它STL组件(比如容器)很好的配合使用。
boost提供的智能指针正好补充了以上功能,比较通用的组件有:
scoped_ptr,用于处理单个对象的唯一所有权。
scoped_array,与scoped_ptr类似,但是用来处理数组的。
shared_ptr,允许共享对象所有权。
shared_array,允许共享数组所有权。
一套处理日期与时间的组件,在它之前,我没有发现有类似的C++库,每次处理时间日期时,都觉繁琐且容易出错,现在用它,就再也不用记浮点数0是哪年哪月哪日了。使用date_time要编译boost代码成相应的库。它的使用比较简单,下面给出例子:
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
void test_data_time()
{
using namespace boost::gregorian;
date today = day_clock::local_day();
std::string strToday = to_simple_string(today);
std::cout << "today is: " << strToday << std::endl;
using namespace boost::posix_time;
date d(2004, May, 1);
ptime t1(d, hours(5)+minutes(4)+seconds(2)+millisec(1));
ptime t2 = t1 - hours(5) - minutes(4) - seconds(2) - millisec(1);
time_duration td = t2 - t1;
std::cout << to_simple_string(t2) << " - "
<< to_simple_string(t1) << " = "
<< to_simple_string(td) << std::endl;
}
[1] 这里是大多数而不是全部,是因为boost不象STL那样已经标准化,主流的编译器要么提供本地版本要么完全支持开源版本。而目前boost的开源版本可能不是每一个编译器都能完全支持;另一个与STL不同的是,boost某些库需要编译成相应的静态库与动态库才可以用。
[3] 其实普通数组的指针也是可以看作为一种简单的iterator