1.3开发环境
由于Boost大量使用了C++高级特性(如模板偏特化、ADL),因此不是所有的编译器都
能够很好地支持Boost。

在VC集成环境中使用嵌入工程编译的方式需要定义宏BOOST_ALL_NO_LIB或者
BOOST_XXX_NO_LIB(XXX是某个库的名称),以指示BOOST库不要使用自动链接功能。

如果在debug版工程,不要忘记在Preprocessor页中定义宏"_STLP_DEBUG"和"
__STL_DEBUG"以使得STLport。

第2章 时间与日期
2.6处理日期
date_time库的日期基于格里高利历,支持从1400-01-01到9999-12-31之间的日期计算,
它不能处理公元前的日期。

boost.smart_ptr库是对C++98标准的一个绝佳补充。它提供了六种智能指针,包括
scoped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr和intrusive_ptr,
从各文件来增强std::auto_ptr,而且是异常安全的。库中的两个类---shared_ptr和'
weak_ptr已被收入到C++新标准的TR1库中。

它们对于所指的类型T仅有一个很小且很合理的要求:类型T的析构函数不能抛出异常。

这些智能指针都位于名字空间boost,为了使用smart_ptr组件,需要包含头文件
<boost/smart_ptr.hpp>。

根据C++标准,对0(空指针)执行delete操作是安全的。

scoped_ptr的成员get(),它返回scoped_ptr内部保存的原始指针,可以在某些要求必
须是原始指针的场景(如底层的C接口)。但要记住,我们不能对这个指针做delete
并且没有置0的操作,否则scoped_ptr析构时会对已经删除的指针再进行操作。

不能对智能指针对象使用delete操作,因为智能指针是一个行为类似指针的对象,而不
是指针,对一个对象应用delete是不允许的。

scoped_ptr与auto_ptr一样,不能用作容器的元素,但原因不同,auto_ptr是因为它的
转移语义,而scoped_ptr则是因为不支持拷贝和赋值,不符合容器对元素类型的要求。

scoped_ptr与auto_ptr一根本性区别在于指针的所有权。auto_ptr特意被设计为指针的
所有权是可转移的,可以在函数之间传递,同一时刻只能有一个auto_ptr管理指针。
而scoped_ptr把拷贝构造函数和赋值函数都声明为私有的,拒绝了指针所有权的转让--
除了scoped_ptr自己,其他任何人都无权访问被管理的指针,从而保证了指针的绝对
安全。

scoped_array弥补了标准库中没有指向数组的智能指针的缺憾。它包装了new[]操作符。
但不推荐使用scoped_array,而推荐使用vector。

3.4 shared_ptr
shared_ptr是最重要的智能指针。它使用引用计数。它可以安全地放到标准容器中。弥补
了auto_ptr因为转移语义而不能把指针作为STL容器元素的缺陷。
shared_ptr的reset()函数的行为与scoped_ptr也不尽相同,它的作用是将引用计数减1
停止对指针的共享,除非引用计数为0,否则不会发生删除操作。带参数的reset()则
类似相同形式的构造函数,原指针引用计数减1的同时改为管理另一个指针。

unique()在shared_ptr是指针的唯一所有者时返回true。use_count()返回当前指针的
引用计数,但use_count()要小心使用,它应该仅仅用于测试或者调试,它不提供高效率
的操作,而且有时候可能不可用的(极少数情形)。而unique()则是可靠的,任何时候
都可用,而且比use_count() == 1速度更快。

shared_ptr还支持相等或不相等,以及operator<比较大小,且都是基于内部保存的指
针。这使用得shared_ptr可以被用于标准关联容器(set和map)。shared_ptr很好地消除
了显式的delete调用。

shared_ptr在头文件<boost/make_shared.hpp>中提供了一个自由工厂函数
make_shared<T>(),来消除显式的new调用,它的名字提供了标准库的make_pair(),
声明如下:
template<class T, class... Args>
shared_ptr<T> make_shared(Args &&... args);

shared_ptr<string> sp = make_shared<string>("make_shared");
shared_ptr<vector<int> > spv = make_shared<vector<int> >(10, 2);

3.4.5 应用于标准容器
有两种方式可以将shared_ptr应用于标准容器(或者容器适配器等其他容器)。
一种用法是将容器作为shared_ptr管理的对象,如shared_ptr<list<T> >,使用容器可
以被安全地共享,用法与普通shared_ptr没有区别。
另一种用法是将shared_ptr作为容器的元素,如vector<shared_ptr<T> >。

记住标准容器不能容纳auto_ptr和scoped_ptr等

3.4.6 应用于桥接模式
桥接模式bridge是一种结构型设计模式,它把类的具体实现细节对用户隐藏起一类。在
具体编程实践中桥接模式也被称为pimpl或者handle/body惯用法。它可以将头文件的
依赖关系降到最小,减少编译时间,而助可以不使用虚函数实现多态。它可以任意改变
具体的实现而外界对此一无所知,也减少了源文件之间的编译依赖。

3.4.8 定制删除器
shared_ptr(Y* p, D d),它涉及shared_ptr的另一个重要概念:删除器。用d来删除,
即把delete p换成d(p)。删除器d可以是函数对象或函数指针,使用得d(p)成立。

shared_ptr提供一个自由函数get_deleter(shared_ptr<T> const&p),它能够返回删除
器的指针。
有了删除器的概念,我们就可以用shared_ptr实现管理任意资源。只要这种资源提供了
它自己的释放操作,shared_ptr就能够保证自动释放。

3.9 pool
pool只能作为普通数据类型如int、double等的内存池,不能应用于复杂的类和对象,因
为它只分配内存,不调用构造函数,这个时候,我们需要使用object_pool。

第4章 实用工具
4.1 noncopyable
4.2 typeof
4.3 optional
optional库使用“容器”语义,包装了“可能产生无效值”的对象,实现了“未初始化”
的概念。
optional很像一个仅能存放一个元素的容器,它实现了“未初始化”的概念:如果元素
未初始化,那么容器就是空的,否则,容器内就是有效的、已初始化的值。
optional的比较是深比较。

4.3.5 工厂函数
optional提供一个类似make_pair()、make_shared()的工厂函数make_optional()。
可以根据参数类型自动推导optional的类型,用来辅助创建optional对象。

optional<T>要求类型T具有拷贝语义,因为它内部会保存值的拷贝。

4.4 assign
STL容器仅提供了容纳这些数据的方法,但填充的步骤却是相当地麻烦,必须重复调用
insert()或push_back()等成员函数,这正是boost.assign出现的理由。

assign库重载了赋值操作符operator+=、逗号操作符operator,和括号操作符operator()

使用assign库时必须使用using指示符,只有这样才能让重载的+=,等操作符在作用域内
生效,如:
#include <boost/assigh.hpp>
int main()
{
using namespace boost::assign; //很重要,启用assign库的功能
vector<int> v;
v += 1,2,3,4,6*6;

set<string> s;
s += "cpp", "java", "python";

map<int, string> m;
m += make_pair(1, "one"), make_pair(2, "two");
}

+=操作符后可以接若干个可被容器容纳的元素,元素之间使用逗号分隔。元素不一定是
常量,表达式或者函数调用也是可以接受的,只要其结果能够转换成容器可容纳的类型。
比较特别的是map容器,必须使用make_pair()辅助函数来生成容器元素。

operator+=很好用,但有一点遗憾,它仅限应用于STL中定义的标准容器,vector,list,
set等,对于其他类型的容器(如boost新容器)则无能为力。

4.4.2 使用操作符()向容器增加元素
assign库还提供了三个辅助函数insert()、push_front()、push_back()。这些函数可
作用于拥有同名成员函数的容器,接受容器变量作为参数,返回一个代理对象
list_inserter。如
vector<int> v;
push_back(v)(1)(2)(3);
map<int, string> m;
insert(m)(1, "one")(2, "two"); // 对于set和map,只能使用assign::insert(),如果
括号中没有参数,则将调用容器元素的缺省构造函数填入一个缺省值,逗号操作符则不
能这样做。

括号操作符也可以与逗号等操作符配合使用,写法更简单,有时甚至看起来不像是合法
的C++代码,例如:
using namespace boost::assign;
vector<int> v;
push_back(v), 1, 2, 3, 4;
push_back(v)(6), 7, 64/8, (9), 10;
//v = [6,7,8,9,10];
set<string> d;
insert(d)() = "cpp", "java", "python";
//d = ["", "cpp", "java", "python"]

4.4.3 初始化容器元素
assign库使用list_of()、map_list_of()/pair_list_of()和tuple_list_of()三个函数
解决了在容器构造的时候就完成数据的填充。

4.4.4 减少重复输入
assign库提供repeat()、repeat_fun()和range()三个函数来减轻工作量。

vector<int> v = list_of(1).repeat(3,2)(3)(4)(5);
//v = 1,2,2,2,3,4,5;
multiset<int> ms;
insert(ms).repeat_fun(5, &read).repeat(2,1),10;
//ms = x,x,x,x,x,1,1,10;
deque<int> d;
push_front(d).range(v.begin(), v.begin()+5);
//d = 3,2,2,2,1

4.4.5 与非标准容器工作
assign库不仅支持全部八个STL标准容器vector,string, deque,list, set, multiset,
map, multimap,也对STL中的容器适配器提供了适当的支持,包括stack,queue和
priority_queue.
因为stackt等容器适配器不符合容器的定义,没有insert、push_back等成员函数,所
以不能使用赋值的方式填入元素,只能使用初始化的方式。并在list_of表达式最后
使用to_adapter()成员函数来适配到非标准容器。如果使用逗号操作符还需要把整个
表达式用括号括起来,才能使用点号调用to_adapter()。
queue<string> q = (list_of("china")("us")("uk")).repeat(2, "russia").
to_adapter();
assign库也支持部分不在STL中定义的非标准容器,如STLport中的slist和hash_set/
hash_map,因为它们符合容器的定义,故用法与标准容器没有什么区别。

此外,assign库还支持大部分Boost库容器,如array, circular_buffer,unordered等。

4.4.6 高级用法
list_of()嵌套使用
list_of()可以就地创建匿名列表,这一点很有用,它可以嵌套在assign库用法中。
例如,下面的代码使用vector构造了了个二维数组,使用list_of(list_of())的嵌套
形式来初始化:
vector<vector<int> > v = list_of(list_of(1)(2))(list_of(3)(4));

v += list_of(5)(6), list_of(7)(8);

4.5 swap
boost::swap是对标准库提供的std::swap的增强和泛化。
为交换两个变量(可以是int等内置数据类型,或者是类实例、容器)的值提供了便捷
的方法。

std::swap()要求交换的对象必须是可拷贝构造和可拷贝赋值的,即重载operator=。但
这种交换是最通用但也是效率最低的方法。

解决方案有两种:一是直接利用函数重载; 二是使用ADL(argument dependent lookup,
参数依赖查找)查找模板特化的swap。这两种方案就是boost::swap的工作原理。

它查找有无针对类型T的std::swap()的特化swap()或者通过ADL查找模板特化的swap(),
如果有则调用,如果两种查找都失败时则退化为std::swap()。此外,boost::swap还
增加了对C++内建数组交换的支持(它要求两个数组大小相同)。

如果读者担心在全局或std名字空间编写自由函数swap会造成名字“污染”,也可以把特化
的swap加入到boost名字空间,或者其他ADL可以找到的名字空间。

4.5.5 使用建议
我们应该尽量使用boost::swap,它提供了比std::swap更好的优化策略。并且你自己写
的类应该总是实现在高效的交换。或者想交换两个数组的内容。

变量值交换是一个基础但很重要的操作,几乎所有boost库组件都实现了自己的swap
成员函数,并且用boost::swap来提高交换的效率,

4.6 singleton
目前boost中并没有专门的单件库,而仅在其他库中有并不十分完善的实现。

4.6.1 boost.pool的单件实现
#include <boost/pool/detail/singleton.hpp>
using boost::details::pool::singleton_default; //单件名字空间
class point
{
public:
point(int a = 0, int b = 0, int c = 0):x(a), y(b), z(c)
{cout<<"point ctor"<<endl;}
~point()
{cout<<"point dtor"<<endl;}
...
};

int main()
{
cout<<"main() start"<<endl;
typedef singleton_default<point> origin; // 定义单件类
origin::instance().print(); // 使用instance()获得单件对象
cout<<"main() finish"<<endl;
}
singleton_default用法非常简单,只需要把想成为单件的类作为它的模板参数就可以了
唯一的不方便之处就是过长的类型名,但可以用typedef来简化。

4.6.2 boost.serialization的单件实现
在序列化库serialization中有另一个单件实现类:singleton,它们于名字空间
boost::serialization。需要包含头文件<boost/serialization/singleton.hpp>

singleton与pool.singleton_default基本相同,对模板参数T也有同样的要求(具有
缺省构造函数,构造析构不抛异常)。

4.7 tribool
在处理tribool的不确定状态时必须要小心,因为它既不是true也不是false,使用它
进行条件判断永远都不会成立,判断不确定状态必须要与indeterminate值比较或者
使用indeterminate()函数。

4.7.3 tribool库默认采用indeterminate作为第三态的名字,很清晰明确但可能有些长。
tribool库使用宏BOOST_TRIBOOL_THIRD_STATE就可以为第三态更名。像这样:
BOOST_TRIBOOL_THIRD_STATE(unknown)。

因为宏BOOST_TRIBOOL_THIRD_STATE实质上定义了一个函数,而C++不允许函数嵌套,所
以这个宏最好在全局域使用,它将在定义后的整个源代码中都生效。

4.7.4 输入输出,tribool需要包含头文件<boost/logic/tribool_io.hpp>就可以像
bool类型一样进行流操作了。

4.8 operators
在C++98标准的std::rel_ops名字空间里提供了四个模板比较操作符!=, >, <=, >=
只需要为类定义了==和<操作符,那么这四个操作符就可以自动实现了。
例如:
#include <utility>
class demo_class
{
public:
demo_class(int n):x(n){}
int x;
friend bool operator<(const demo_class& l, const demo_class&r)
{return l.x < r.x;}
};

int main()
{
demo_class a(10), b(20);
using namespace std::rel_ops; //打开std::rel_ops名字空间
cout<<(a<b)<<endl;
cout<<(b>=a)<<endl; //>=等操作符被自动实现
}

operators位于名字空间boost,为了使用operators组件,需要包含头文件<boost/
operators.hpp>,

4.8.1 基本运算概念
operators库是由多个类组成,分别用来实现不同的运算概念,比如less_than_compparable
定义了<系列操作符,left_shiftable定义了<<系列操作符。

我们可以使用多重继承来获得多个操作符的重载。

4.8.3 基类链
4.8.5 相等与等价
相等equality与等价equivalent是两个极易被混淆的概念。一个简单的解释是:相等是
基于操作符==,即x==y;而等价基于<,即!(x<y)&&!(x>y)。
但对于大多数复杂类型和自定义类型,==和<操作符是两个不同的运算。
如p1(1,2,3)和p3(3,2,1)两者完全不相等,但等价。

标准库中的关联容器set,map和排序算法使用的是等价关系<操作符,而各种查找算法
find使用的是相等关系的==操作符。

4.8.6 解引用操作符
operators库使用dereferenceable提供了对解引用操作符*,->的支持,它的用法与之前
介绍的算术操作符不太相同。

dereferenceable类要求子类提供operator*,会自动实现operator->。与前面的算术
操作符不同的是它不是使用的友元函数,因此在使用dereferenceable时必须使用
public继承,否则operator->将会成为类的私有成员函数,外界无法访问。

4.8.7 下标操作符
operators库使用indexable提供下标操作符[]的支持,它也属于解引用的范畴。用法与
dereferenceable很相似。
indexable要求子类提供一个operator+(T, I)的操作定义,类似于一个指针的算术运算

4.9 exception
4.9.1标准库中的异常
为了使用boost.exception,我们需要先了解C++98标准规定的异常体系。
C++98标准中定义了一个异常基类std::exception和try/catch/throw异常处理机制,
std::exception又派生出若干子类。用以描述不同种类的异常,如bad_alloc,
bad_cast,out_of_range等等,共同构建了C++异常处理框架。

C++允许任何类型作为异常抛出,但在std::exception出现后,我们应该尽量使用它,因
为std::exception提供了一个很有用的成员函数what(),可以返回异常所携带的信息,
这比简单地抛出一个整数错误值或者字符串更好、更安全。

如果std::exception及其子类不能满足程序对异常处理的要求,我们也可以继承它,为
它添加更多的异常诊断信息。

4.9.2类摘要
exception库提供两个类:exception和error_info,它们是exception库的基础。

exception是一个抽象类,它的重要能力在于其友元操作符<<,可以存储error_info对象
的信息,存入的信息可以用自由函数get_error_info<>()随时再取出来。

应该总对异常类使用虚继承。这是由于异常的特殊处理机制决定的。从现在开始,对
boost::exception应该总使用虚继承。

因为exception被定义为抽象类,因此我们的程序必须定义它的子类才能使用它,如前
所述exception必须使用虚继承的方式。通常,继承完成后自定义异常类的实现也就结
束了,不需要再添加成员变量或成员函数,这些工作都已经由exception完成了。
如:
struct my_exception:
virtual std::exception, // 虚继承
virtual boost::exception
{}; //空实现,不需要实现代码

4.10 uuid
uuid库是一个小的实用工具,可以表示和生成UUID.
UUID是Universally Unique Identifier的缩写,它是一个128位的数字(16字节),不
需要有一个中央认证机构就可以创建全球唯一的标识符
UUID的另一个别名是GUID,在微软的COM中被广泛使用,用于标识COM组件接口。

4.10.2用法
uuid是一个很小的类,它特意被设计为没有构造函数,可以像POD数据类型一样使用。
uuid内部使用一个16字节的数组data作为uuid值的存储,这个数组是public的,因此
可以任意访问,比如拷贝或者赋值。
可以把uuid看作是一个容量固定为16、元素类型为unsigned char的容器。

UUID的生成有不同的算法,这些算法使用枚举version_type来标识,version()函数可以
获得UUID的算法版本。uuid类可以识别现有的五种生成算法,分别是:
一、基于时间的MAC的算法 version_time_based
二、分布计算环境算法 dce_security
三、MD5摘要算法 version_name_based_md5
四、随机数算法 version_random_number_based
五、SHA1摘要算法 version_name_based_sha1

在数量宠大的UUID中有一个特殊的全零值nil,它表示一个无效的UUID,成员函数
is_nil()可以检测uuid是否是nil。

4.10.3 生成器
uuid库提供了四种生成器,分别是Nil生成器、字符串生成器、名字生成器和随机生成
器。它们都是函数对象,重载了operator(),可以直接调用生成uuid对象。

Nil生成器
只能生成一个无效的UUID值(即全零的UUID).
Nil生成器的类名是nil_generator,另外有一个内联函数nil_uuid(),相当于直接调用
了Nil生成器。
如:
uuid u = nil_generator()();
asseert(u.is_nil());

u = nil_uuid();
assert(u.is_nil());
注意,代码的第一行,在nil_generator类名后面出现了两对圆括号,不熟悉函数对象
的读者可能会不太理解。它的语义解析是:第一对圆括号与nil_generator结合,结果
是调用nil_generator的构造函数,生成一个临时对象,然后第二对圆括号是
nil_generator对象的operator()操作符重载,就像是一个函数调用,产生了一个
nil uuid对象。

字符串生成器
string_generator

名字生成器
name_generator使用基于名字的SHA1摘要算法。它需要先指定一个基准UUID,然后使用
字符串名字派生出基于这个UUID的一系列UUID。名字生成器的典型的应用场景是为一个
组织内的所有成员创建UUID标识,只要基准UUID不变,那么相同的名字总会产生相同的
UUID。
例如:
uuid www_xxx_com = string_generator()("0123456789abcdef0123456789abcdef");
name_generator ngen(www_xxx_com);

uuid u1 = ngen("mario");

uuid u2 = ngen("link");

随机数生成器
random_generator rgen;
uuid u = rgen();

4.10.4 增强的uuid类
uuid类为了追求效率而没有提供构造函数,要生成一个UUID值必须要使用生成器。我们
可以从uuid类派生一个可以自动产生UUID值的增强类,以简化UUID的使用。

4.10.6
uuid的名字生成器使用了SHA1摘要算法,该算法可以将任意长度的文本压缩成一个只有
20字节(160位)的独一无二的摘要。

sha1算法位于名字空间boost::uuids::detail,使用时需要包含头文件<boost/uuid/
sha1.hpp>
sha1类的用法很简单,使用成员函数process_byte()、process_block()和process_types()
以不同的方式把数据“喂”给sha1对象,当输入所有数据后用get_digest()获得计算
出的摘要值。

4.11 config
config库主要是提供给boost库开发者(而不是库用户)使用。它将程序的编译配置
分解为三个正交的部分:平台、编译器和标准库。

4.11.1 BOOST_STRINGIZE
宏BOOST_STRINGIZE可以将任意字面量转换为字符串。它在头文件
<boost/config/suffix.hpp>中
但它是宏,不支持运行时转换。如果要在运行时转换数字或者其他变量到字符串,请
使用之后的lexical_cast。

4.11.2 BOOST_STATIC_CONSTANT
C++98标准允许直接在类声明中为静态整形成员变量赋初始值。

4.12 utility
utility库不是一个有统一主题的Boost库,而是包含了若干个很小但有用的工具。

本章开头介绍的noncopyable、swap都被归类在utility库里,此外utility还包括其他
很多个实用类,如checked_delete、compressed_pair、base_from_memeber等。

4.12.1 BOOST_BINARY
BOOST_BINARY提供一组宏,用于实现简单的二进制常量表示。

它使用boost.preprocessor预处理元编程工具将一组或多组01数字在编译期展开成为一
个八进制数字。每个数字组之间可以用空格分隔,特别注意的是,数字组的长度一定不
能超过八个,由于预处理器宏展开的限制,嵌套层次太深会导致无法通过编译,报出
一大堆错误。

4.12.2 BOOST_CURRENT_FUNCTION
微软编译器VC在C89的__FILE__和__LINE__之外定义了一些扩展宏,其中的__FUNCTION__
宏可以表示函数名称,GCC、intel C等编译器也定义有类似的宏。而C99标准则定义了
__func__宏以实现同样的功能。但目前的C++98标准不能这样做。

在STL中,std::bitset不能直接使用字符串构造,必须使用bitset<5>(string("10110"))
这样的形式,导致生成一个string临时变量,效率较低。

它在头文件#include <boost/current_function.hpp>
只需要在代码中使用BOOST_CURRENT_FUNCTION宏,就可获得包含该宏的外围函数名称,
它表现为一个包含完整函数声明的编译期字符串。

应当总用boost::noncopyable的名字空间域限定形式来使用它,而不是用using语句,
避免在头文件打开boost名字空间。

在使用optional存储的元素之前,必须测试它的有效性。

assign库对标准容器、容器适配器和Boost容器都提供了很全面的支持,这使得它具有
很高的使用价值。

如果想要自己的类安全高效,那么应该提供一个好的swap函数,它是很多实用功能的
基础。如果你自己的类实现了高效的交换方法,那么boost::swap就会自动调用它。

Boost库提供了两种实现singleton的方式:pool.singleton和serialization.singleton。
两者都要求模板类型参数T具有缺省构造函数,而且构造的析构时不能抛出异常。

tribool实现了三态布尔逻辑。

operator库中的equality_comparable、less_than_comparable和totally_ordered是最
常用的操作符重载类,它们提供比较运算,被用于支持标准容器。

uuid组件实现了对UUID的表示和处理,提供了基于名字和随机数的生成算法生成全球
唯一的标识符,可以用在很多地方来唯一地标识对象。uuid库里还附带了一个SHA1算法
的实现,能够为任意长度的数据生成SHA1摘要。

最后是config和utility库,它们提供了几个很有用的宏:BOOST_STRINGIZE实现编译期
字符串转换;BOOST_STATIC_CONSTANT可以定义类的静态整型成员常量;BOOST_BINARY
便利了二进制数字的书写方法;BOOST_CURRENT_FUNCTION能够输出函数名称字符串。
还有头文件<boost/config/warning_disable.hpp>可以禁止编译器的C4996警告。

第5章
字符串与文本处理
字符串与文本处理一直是C++的弱项。虽然C++98标准提供了一个标准字符串类std::string
暂解燃眉之急,但C++仍然缺乏很多文本处理的高级特性,如正则表达式、分词等等,
使得不少C++程序员不得不转向其他语言(如perl、python)。

Boost中五个字符串与文件处理领域的程序库。首先是两个与C标准库函数功能类似的
lexical_cast和format,它们关注于字符串的表示,可以将数值转化为字符串,对输
出做精确的格式化。string_algo库提供了大量常用的字符串处理函数。剩下的可以
使用tokenizer和xpressive,前者是一个分词器,而后者则是一个灵活且功能强大的
正则表达式分析器,同时也是一个语法分析器。

使用Boost,C++中文本处理的一切问题将迎刃而解。

5.1 lexical_cast
lexical_cast库进行“字面量”的转换,类似C中的atoi函数,可以进行字符串、整数/
浮点数之间的字面转换。

lexical_cast位于名字空间boost,为了使用lexical_cast组件,需要包含头文件
<boost/lexical_cast.hpp>

Unicode和区域字符编码转换逐渐成为了软件开发中无法回避的问题。C++98标准定义了
wchar_t和locale等概念,C++0X还专门引入了<codecvt>。另外Boost的很多字符串的
处理都需要标准库的<locale>。

5.1.1用法
C语言中的atoi()、atof()系列函数,它们可以把字符串转换成数值,但不存在如itoa()
这样的返回转换(C语言标准未提供,但有的编译器厂商会提供非标准的如_itoa())。

使用lexical_cast可以很容易地在数值与字符串之间转换,只需要在模板参数中指定
转换的类型即可,

lexical_cast不能转换如"133L"、"0x33"这样的C++语法许可的数字字面量字符串。而且
lexical_cast不支持高级的格式控制,不能把数字转换成指定格式的字符串,如果需要
更高级的格式控制,可使用std::stringstream或者boost::format。

lexical_cast内部使用了标准库的流操作,因此,对于它的转换对象,有如下要求,
下义了operator<<, operator>>和是可缺省构造的可拷贝构造的。
C++中的内建类型都满足这些。

5.2 format
boost.format实现在类似于printf()的格式化对象,可以把参数格式化到一个字符串,
而且是完全类型安全的。

format组件位于名字空间boost,在头文件<boost/format.hpp>中。

boost.range基于STL迭代器提出了“范围”的概念,是一个容器的半开空间。

5.3 string_algo
string_algo库主要的工作对象还是字符串string和wstring,它也可以工作在标准容器
vector、deque、list和非标准容器slist、rope上,

前缀i:
后缀_copy
后缀_if

string_algo库提供的算法共分五大类,如下:
大小写转换
判断式与分类
修剪
查找与替换
分割与合并

5.3.3 大小写转换
包括两组算法:to_upper()和to_lower()。
还有前缀i和后缀_copy的版本。

5.3.4 判断式(算法)
starts_with
ends_with
contains
equals
lexicographical_compare
all

除了all,这些算法都有另一个i前缀的版本。由于它们不变动字符串,因此没有_copy
版本。
它们同时还有一种接受比较谓词函数对象的三参数版本,而没有使用_if后缀。

5.3.5 判断式(函数对象)
string_algo增强了标准库中的equal_to<>和less<>函数对象,允许对不同类型的参数
进行比较,并提供大小写无关的形式。这些函数对象有
is_equal
is_less
is_not_greater

5.3.6 分类
string_algo库的分类函数如下:
is_space
is_alnum
is_alpha
is_cntrl
is_digit
is_lower
is_graph
is_print
is_punct
is_upper
is_xdigit
is_any_of
if_from_range: 字符是否位于指定区间内,即from<=ch<=to
这些函数并不真正地检测字符,而是返回一个类型为detail::isclassifiedF的函数对象
这个函数对象的operator()才是真正的分类函数(因此,它些函数都属于工厂函数)。
函数对象is_classifiedF重载了逻辑运算符||,&&,和!,可以使用逻辑运算符把它们组
合成逻辑表达式,以实现更复杂的条件判断。当然,我们也可以自定义判断式。

5.3.7 修剪
string_algo提供3个修剪算法:trim_left, trim_right和trim
它有_if和_copy两种后缀,因此每个算法都有四个版本。

5.3.8 查找
string_algo使用了boost.range库的iterator_range返回查找到的整个区间。
string_algo提供的查找算法包括:
find_first
find_last
find_nth
find_head
find_tail
这些算法都不变动字符串,因此没有_copy后缀版本。但其中前三个算法有i前缀版本。
iterator_range可以像标准容器一样判断是否为空,也可以隐式转换为bool值。

string_algo库还有另外三个查找算法:find_token,find_regex和通用的find算法。

5.3.9 替换与删除
replace/erase_first
replace/erase_last
replace/erase_nth
replace/erase_all
replace/erase_head
replace/erase_tail
前八个算法每个都有前缀i、后缀_copy和组合,有四个版本,后四个则只有后缀_copy
的两个版本。

5.3.10 分割
string_algo提供两个字符串分割算法find_all和split
find_all算法类似于普通的查找算法,它搜索所有匹配的字符串,加入到容器中,有一
个忽略大小写的前缀i版本。

split算法使用判断式Pred来确定分割的依据,如果字符ch满足判断式Pred(Pred(ch)
==true),那么它就是一个分割符,将字符串从这里分割。

参数eCompress可以取值为token_compress_on或token_compress_off,如果值为前者,
那么当分隔符连续出现时将被视为一个,如果为token_compress_off则两个连续的分隔
符标记了一个空字符串。参数eCompress默认取值为token_compress_off。

5.3.11 合并
join还有一个后缀_if的版本,它接受一个判断式,只有满足判断式的字符串才能参与
合并。

5.3.12 查找(分割)迭代器
string_algo库还提供两个查找迭代器find_iterator和split_iterator,它们可以在字
符串中像迭代器那样遍历匹配,进行查找或者分割,无需使用容器来容纳。

使用查找迭代器首先要声明迭代器对象find_iterator或split_iterator,它们的模板
类型参数是一个迭代器类型a,例如string::iterator或char*

为了获得迭代器的起始位置,我们需要调用first_finder()函数,它用于判断匹配的对
象,然后再用make_find_iterator或make_split_iterator()来真正创建迭代器。同
族的查找函数还有last_finder,nth_finder,token_finder等。
例如:
typedef split_iterator<string::iterator> string_split_iterator;
string_split_iterator p, endp;
for (p = make_split_iterator(str, first_finder("||", is_iequal()));
p != endp; ++p)
{
cout<<"["<<*p<<"]";
}

5.4 tokenizer
tokenizer库一个专门用于分词tiken的字符串处理库,可以使用简单易用的方法把一个
字符串分解成若干个单词。
<boost/tokenizer.hpp>
using namespace boost;

tokenizer接受三个模板类型参数,分别是:
TokenizerFunc :tokenizer库专门的分词函数对象,默认是使用空格和标点分词
Iterator: 字符序列的迭代器类型
Type: 保存分词结果的类型

tokenizer的构造函数接受要进行分词的字符串,可以以迭代器的区间形式给出,也可
以是一个有begin()和end()成员函数的容器。
assign()函数可以重新指定要分词的字符串,用于再利用tokenizer。
tokenizer具有类似标准容器的接口,begin()函数使用tokenizer开始执行分词功能,
返回第一个分词的迭代器,end()函数表明迭代器已经到达分词序列的末尾,分词结束。

5.4.2 用法
tokenizer的用法很像string_algo的分割迭代器,但要简单一些。可以像使用一个容器
那样使用它,向tokenizer传入一个欲分词的字符串构造,然后用begin()获得迭代器
反复迭代。

tokenizer默认把所有的空格和标点符号作为分隔符,因此分割出的只是单词。

5.5 xpressive
许多编程语言都提供了内置的正则表达式,如Perl,Python,Java,但C++98中没有。
静态方式 <boost/xpressive/xpressive_static.hpp>
动态方式 <boost/xpressive/xpressive_dynamic.hpp>
两种方式 <boost/xpressive/xpressive.hpp>

\d匹配数字(相当于[0-9]),\w匹配字母(相当于[a-z]),\s匹配空格等。

由于C++没有如C#和Python那样提供“原始字符串”的表达式,因此在C++代码中要使用
双斜杠。建议在代码中使用正则表达式时,一定要在语句前用注释说明其原始表达式
以方便将来调试和维护。

5.5.3 类摘要
介绍xpressive库提供的3个重要的类,分别是:basic_regex, match_results和
sub_match。

typedef basic_regex<std::string::const_iterator> sregex;
typedef basic_regex<char const *> cregex;

sregex用于操作标准字符串类std::string,cregex用于操作字符数组(C风格字符串)

match_results为正则表达式的匹配结果提供一个类似容器的视图,可以用size()和
empty()判断匹配结果中子表达式的数量,operator[]返回第i个子表达式。如果i==0
则返回整个表达式的匹配对象。

sub_match
模板类sub_match继承自std::pair。可以把它当作一个字符串的区间表示。

5.5.4 匹配
自由函数regex_match()用来检查一个字符串是否完全匹配一个正则表达式,返回一个
bool结果。

最常用的有两种形式:
bool regex_match(String, basic_regex const &re);
bool regex_match(String, match_results& what, basic_regex const &re);

cregex reg = cregex::compile("a.c");
使用cregex类的工厂方法compile()创建了一个正则表达式对象reg。

using namepsace boost::xpressive;
cregex reg = cregex::compile("\\d{6}((1|2)\\d{3}))", icase);
cmatch what;
assert(regex_match("9995551970010199", what, reg));
for (BOOST_AUTO(pos, what.begin()); pos != what.end(); ++pos)
{
cout<<"["<<*pos<<"]";
}
cout<<endl;
cout<<what[1]<<what[3]<<endl;

regex_match()还可以替代部分string_algo库的判断式算法的功能,例如下面的代码
使用sregex实现了starts_with和ends_with算法。
string str("readme.txt");
sregex start_reg = sregex::compile("^re.*");
sregex end_reg = sregex::compile(".*.txt$"); //匹配末尾
assert(regex_match(str, start_reg));
assert(regex_match(str, end_reg));

5.5.5 查找
为了使用正则表达式的查找功能,我们要用到regex_search()。
它与regex_match()的区别是
regex_match()要求输入字符串必须要与正则表达式完全匹配,而regex_search()则检测
输入表达式中是否包含正则表达式,即存在一个匹配正则表达式的子串。

因此,regex_search()比regex_match()使用起来更加灵活。

sting str("readme.TXT");

sregex start_reg = sregex::compile("^re")
sregex end_reg = sregex::compile("txt$", icase);

assert(regex_search(str, start_reg)); // starts_with
assert(regex_search(str, end_reg)); // ends_with
assert(regex_search(str, cregex::compile("me"))); // contains

5.5.6 替换
xpressive的替换功能使用的函数是regex_replace(),它先使用正则表达式查找匹配的
字符串,然后再用指定的格式替换。
string regex_replace(string, basic_regex const &re, Format);
第三个参数Format可以是一个简单字符串,也可以是一个符合ECMA_262定义的带格式的
字符串,它可以用$N引用正则表达式匹配的子表达式,$&引用全匹配。在替换完成后,
regex_replace()返回一个字符串的拷贝。

5.5.7 迭代
xpressive库提供一个强大的迭代器模板类regex_iterator<>,它类似string_algo的
查找迭代器和tokenizer,提供一个类似迭代器的视图(不是容器)。在具体应用时,我
们应当使用它的typedef,如sregex_iterator和cregex_iterator。

示范regex_iterator<>用法的代码如下:
using namespace boost::xpressive;
string str("Power-bomb, power-suit, pOWER-beam all items\n");
sregex reg = sregex::compile("power-(\\w{4})", icase);
sregex_iterator pos(str.begin(), str.end(), reg);
sregex_iterator end;
while(pos != end)
{
cout<<"["<<(*pos)[0]<<"]";
++pos;
}
cout<<endl;
}

5.5.8 分词
xpressive库使用模板类regex_token_iterator<>提供了强大的分词迭代器,要比
string_algo和tokenizer的分词能力强大,灵活得多。

regex_token_iterator<>同样不能直接使用,你需要用它的两个typedef: 
sregex_token_iterator和cregex_token_iterator。
regex_token_iterator<>的用法大致与regex_iterator<>相同,但它的变化在于匹配
对象的使用方式。

默认情况下regex_token_iterator<>同regex_iterator<>,返回匹配的字符串。如果
构造时传入-1作为最后一个args参数的值,它将把匹配的字符串视为分隔符。如果args
是一个正数,则返回匹配结果的第args个字串。

还有一点与regex_iterator<>不同的是它解引用返回的是一个sub_match对象,而不是
match_results对象。

char * str = "*Link*||+Mario+||Zelda!!!||Metroid";
// 使用分隔符正则表达式,分隔符是"||"
cregex split_reg = cregex::compile("\\|\\|");
cregex_token_iterator pos(str, str+strlen(str), split_reg, -1);

while(pos != cregex_token_iterator())
{
cout<<"["<<*pos<<"]";
++pos;
}
cout<<endl;
}

5.5.9 与regex的区别
boost.regex是Boost库中另一个正则表达式解析库,xpressive的形式受到了它的很大
影响,接口几乎与boost.regex完全相同。

5.5.10 高级议题
工厂类
除了可以使用sregex::compile()创建正则表达式对象外,xpressive还提供一个专门
的工厂类regex_compiler<>。

同样,我们不能直接使用regex_compiler,而是使用它预定义的typedef,如
sregex_compile和cregex_complier。

regex_compiler也可以创建正则表达式对象,但比regex::compile()有更多的功能,可
以传入特定的locale支持本地化,并重载operator[]实现了flyweight模式,可以把
它当作一个正则表达式对象池。

cregex_compiler rc; // 一个正则表达式工厂
rc["reg1"] = rc.compile("a|b|c"); // "reg1"关联一个正则表达式
rc["reg2"] = rc.compile("\\d*");  // "reg2"关联一个正则表达式

assert(!regex_match("abc", rc["reg1"]));
assert(regex_match("123", rc["reg2"]));

异常
当xpressive在编译不正确的正则表达式或者执行其他操作出错时会抛出regex_error异
常。

在使用xpressive时应当总是使用try-catch块,以防止异常。

格式化器
在使用regex_replace()进行替换时,除了使用简单字符串和格式字符串,还可以使用
格式化器。格式器是一个具有operator()的可调用对象,函数指针、函数对象都可以,
它必须能够处理查找到的match_results对象。

下面的代码定义了一个函数对象formater,它将查找到的cmatch对象全部改为大写,使用
了string_algo库的to_upper_copy算法。

struct formater
{
string operator()(cmatch const &m) const
{
return boost::to_upper_copy(m[0].str());
}
};
char *str = "*Link*||+Mario+||Zelda!!!||Metroid";
cregex reg = sregex::compile("\\w+", icase);
cout<<regex_replace(str, reg, formater())<<endl;

让正则表达式执行得更快
与format库类似,编译一个正则表达式需要很多的运行时开销,应当少做sregex/cregex
对象的创建工作,尽量重用。

同样,smatch/cmatch也应当尽量重用。它们缓存在动态分配的内存,不至于每次重新
分配内存。
下面代码定义了一个静态正则表达式,它等价于"^\d*\w+":
cregex reg = '^'>>*_d>>+_w; // 使用operator>>连接表达式

第6章 正确性与测试
但C\C++只提供了很有限的正确性验证/测试支持---assert宏(没错,它是一个宏,虽然
它违背常识使用了小写的形式),这是很不够的。C++98标准中的std::exception能够
处理运行时异常、但并不能检查代码的逻辑。

Boost在这方面前进了一大步:boost.assert库增强了原始的运行时assert宏,
static_assert库提供了静态断言(编译期诊断),而boost.test库则构建了完整的单元
测试框架。

6.1.1 基本用法
默认情况下BOOST_ASSERT宏等同于assert宏,断言表达式为真。

BOOST_ASSERT宏仅会在debug模式下生效,在release模式下不会进行编译,不会影响
到运行效率,所以可以放心大胆地在代码中使用BOOST_ASSERT断言。

例如下面的用法:
BOOST_ASSERT(x != 0 && "divided by zero"); // 断言参数非0

上面宏BOOST_ASSERT的表法,除了要检查表达式x != 0,还使用逻辑与操作符增加了
断言的描述信息。当断言操失败时,可以给出更具描述性的文字,有助于排错。

6.1.2 禁用断言
如果在头文件<boost/assert.hpp>之前定义了宏BOOST_DISABLE_ASSERTS,那么
BOOST_ASSERT将会定义为((void)0),自动失效。但标准的assert宏并不会受影响,这
可以让程序员有选择地关闭BOOST_ASSERT。

6.1.3 扩展用法
如果在头文件<boost/assert.hpp>之前定义了宏BOOST_ENABLE_ASSERT_HANDLER,这将导
致BOOST_ASSERT的行为发生改变:

它将不再等同于assert宏,断言的表达式无论是在debug还是release模式下都将被求
值,如果断言失败,会发生一个断言失败的函数调用boost::assertion_failed()---这
相当于提供了一个错误处理handle。

函数assertion_failed()被声明在boost名字空间里,但被特意设计为没有具体实现。
用户需要自己实现assertion_failed()函数。以恰当的方式处理错误--通常是记录日志
或者抛出异常。

void assert_failed(char const *expr, char const *function, char const *file,
long line)
{
boost::format fmt("Assertion failed! \n Expression: %s \n Function: %s \n 
File: %s \n Line: %ld\n\n");
fmt % expr% function% file% line;
cout<<fmt;
}

6.1.4 BOOST_VERIFY
它具有与BOOST_ASSERT一样的行为,仅有一点区别:断言的表达式一定会被求值。
使用BOOST_VERIFY需要注意,它在release模式下同样会失效。程序最好不应该依赖于
它的副作用。

6.2 static_assert
使用BOOST_STATIC_ASSERT时要小心,断言的表达式必须能够在编译期求值。
BOOST_ASSERT(assert)必须是一个能够执行的语句,它只能在函数域里出现,而
BOOST_STATIC_ASSERT则可以出现在程序的任何位置:名字空间域、类域或函数域。

BOOST_STATIC_ASSERT(sizeof(empty_class) == 1);
空类其实并不空,因为C++不允许大小为0的类或对象存在,通常的“空类”会由编译器
在里面安插一个类型为char的成员变量,令它在一个确定的大小。

6.2.2 使用建议
BOOST_STATIC_ASSERT主要在泛型编程或模板元编程中有于验证编译期常数或者模板类型
参数。

6.3 test
BOOST_ASSERT

6.2 static_assert
assert宏和BOOST_ASSERT宏是用于运行时的断言,应该在程序中被广泛应用。

static_assert库把断言的诊断时刻由运行期提前到编译期,让编译器检查可能发生的
错误。

static_assert库定义了宏BOOST_STATIC_ASSERT,用来进行编译期断言。
BOOST_STATIC_ASSERT是一个编译期断言,使用了typedef和模板技术实现,它和
BOOST_ASSERT很相似,最重要的区别是使用范围,BOOST_ASSERT(assert)必须是一个
能够执行的语句,它只能在函数域里出现,而BOOST_STATIC_ASSERT则可以出现在程序
的任何位置:名字空间域、类域或函数域。

在C++中“空类”其实并不空,因为C++不允许大小为0的类或对象的存在,通常的“空类”
会由编译器在里面安插一个类型为char的成员变量,令它有一个确定的大小。

BOOST_STATIC_ASSERT主要在泛型编程或模板元编程中用于验证编译期常数或者模板类型
参数。
使用BOOST_STATIC_ASSERT时一定要小心,断言的表达式必须能够在编译期求值。这也正
是迈向C++泛型编程和模板元编程的第一步。

6.3 test
<boost/test/unit_test.hpp>

6.3.2 最小化的测试套件
test库提供一个最小化的测试套件minimal test,只需包含文件<boost/test/minimal.hpp>
它只提供最基本的单元测试功能,没有UTF那么强大,不支持多个测试用例,能够使用
测试断言也很少。
BOOST_CHECK(predicate)
BOOST_REQUIRE(predicate)
BOOST_ERROR(message)
BOOST_FAIL(message)

6.3.3 单元测试框架简介
test库提供了强有力的单元测试框架(UTF),

6.3.4 测试断言
test库中的断言形式是BOOST_XXX_YYY,具体命名规则如下:
BOOST_:遵循Boost库的命名规则,宏一律以大写的BOOST开头。
XXX:断言的级别。WARN是警告级,不影响程序的运行,也不增强错误数量;CHECK是检查
级别,会增加错误数量,但不影响程序运行;REQUIRE是最高级别,如果断言
失败将增加错误数量并终止程序运行。最常用的是CHECK。
YYY:各种具体的测试断言,如断言相等/不等、抛出/不抛出异常、大于或小于等。

test库中最常用的几个测试断言如下:
BOOST_XXX_EQUAL(1,r): 检查l==r,当测试失败时给出详细的信息。它不能用于浮点数
的比较,浮点数的相等比较应使用BOOST_XXX_CLOSE;
BOOST_XXX_GE(l,r):检查l>=r,同样还有GT(L>R),LT(l<r),LE(l<=r)和NE(l!=r),它们
用于测试各种不等性。

BOOST_XXX_THROW(expr, exception):检测表达式expr抛出指定的exception异常

BOOST_XXX_NO_THROW(expr, exception):检测表达式expr不抛出指定的exception
异常。

BOOST_XXX_MESSAGE(expr, message):它与不带MESSAGE后缀的断言功能相同,但测试
失败时给出指定的消息

BOOST_TEST_MESSAGE(message):它仅输出通知用的信息。默认情况不会显示。

6.3.5 测试用例与套件
test库将测试程序定义为一个测试模块,由测试安装、测试主体、测试清理和测试运行
器四个部分组成。测试主体是测试模块的实际运行部分,由测试用例和测试套件组
织成测试树的形式。

测试用例是一个包含多个测试断言的函数,它是可以被独立执行测试的最小单元,各个
测试用例之间是无关的,发生的错误不会影响到其他测试用例。

要添加测试用例,需要向UTF注册。在test库中,可以采用手工或者自动两种形式,
通常我们使用自动和方式。
我们使用BOOST_AUTO_TEST_CASE声明函数一样创建测试用例。
例如
BOOST_AUTO_TEST_CASE(t_case1)
{
BOOST_CHECK_EQUAL(1,1);
...
}

测试套件是测试用例的容器,它包含一个或多个测试用例,可以将繁多的测试用例分组
管理,共享/清理代码,更好地组织测试用例。测试套件可以嵌套,并且没有嵌套层数
的限制。

测试套件同样可以有手工和自动两种使用方式,自动方式使用两个宏BOOST_AUTO_TEST
_SUITE和BOOST_AUTO_TEST_SUITE_END。

这两个宏必须成对使用,宏之间的所有测试用例都属于这个测试套件。一个c++源文件中
可以有任意多个测试套件,测试套件也可以任意嵌套,没有深度的限制。测试套件的名
字一般以s开头。例如:
BOOST_AUTO_TEST_SUITE(s_suite1) // 测试套件开始
BOOST_AUTO_TEST_CASE(t_case1)
{
BOOST_CHECK_EQUAL(1,1);
...
}

BOOST_AUTO_TEST_CASE(t_case2)
{
BOOST_CHECK_EQUAL(5, 10/2);
...
}
BOOST_AUTO_TEST_SUITE_END() // 测试套件结束

任何一个UTF单元测试程序都必须存在一个主测试套件,它是整个测试树的根节点,其他
测试套件都是它的子节点。

主测试套件的定义可以使用宏BOOST_TEST_MAIN或者BOOST_TEST_MODULE,定义了这个宏
的源文件中不需要再有宏BOOST_AUTO_TEST_SUITE和BOOST_AUTO_TEST_SUITE_END,所
有的测试用例都自动属于主测试套件。

6.3.6 测试实例
首先要保证我们已经编译了test库,如前,建立一个test_main.cpp,包含有test库
的编译源码。这个文件就是test库的预编译源程序,含有一个空的测试套件。一旦写
好,不应用再变动它。

6.3.7 测试夹具
测试用例和测试套件构成了单元测试的主体,可以满足大部分单元测试的功能需求,但
有的时候这些还不够,因为它们不能完成测试安装和测试清理的任务。

测试安装和测试清理的动作很像C++中的构造函数和析构函数,因此可以定义一个辅助类
它的构造函数和析构函数分别执行测试安装和测试清理,之后,我们就可以在每个测试
用例最开始声明一个对象,它将自动完成测试安装和测试清理。

基于这个基本原理,UTF中定义了“测试夹具”的概念,它实现了自动的测试安装和测
试清理。就像是一个夹在测试用例和测试套件两端的夹子。测试夹具不仅可以用于测试
用例,也可以用于测试套件和单元测试全局。

使用测试夹具,必须要定义一个夹具类,它只有构造函数和析构函数,用于执行测试
安装和测试清理。基本形式如下:
struct test_fixture_name
{
test_fixture_name(){} // 测试安装工作
~test_fixture_name(){} // 测试清理工作
};

指定测试用例和测试套件的夹具类需要使用另外两个宏:
#define BOOST_FIXTURE_TEST_SUITE(suite_name, F)
#define BOOST_FIXTURE_TEST_CASE(test_name, F)
它们替代了之前的BOOST_AUTO_TEST_CAST和BOOST_AUTO_TEST_SUITE宏,第二个参数指
定了要使用的夹具类。

6、3、8 测试日志
测试日志是单元测试在运行过程中产生的各种文本信息,包括警告、错误和基本
信息,默认情况下这些测试日志都被定向到标准输出stdout。测试日志不同于测试报告
后者是对测试日志的总结。

默认情况下,UTF的日志级别是warning,会输出大部分单元测试相关的诊断信息,但
BOOST_TEST_MESSAGE宏由于是message级别,它的信息不会输出。
日志级别可以通过接下来介绍的单元测试程序的命令行参数改变。

6.3.9 运行参数
UTF的命令行参数基本格式是:
--arg_name=arg_value
参数名称和参数值都是大小写敏感的,并且==两边和--右边不能有空格。

测试泛型代码
UTF也能够测试模板函数和模板类,这对于泛型库开发是非常有用的。
如果采用自动测试方式,可使用BOOST_AUTO_TEST_CASE_TEMPLATE,它需要使用模板元
编程库mpl,声明如下:
#define BOOST_AUTO_TEST_CASE_TEMPLATE(test_name, type_name, TL)

VC下使用test库的一个小技巧
test库在文档中向库用户提供了一个在VC系列开发环境使用test库的一个小技巧,可以
在工程选项build-event中设置post-build,加入命令:
"$(TargetDir)\$(TargetName).exe" --result_code=no --report_level=no
这样在VC编译完成后可以立刻运行单元测试,并且在Output窗口显示出未通过的测试
断言,可以用双击的方式快速跳到断言的位置,提高测试的工作效率。

6.4 总结
很多软件方法学如敏捷开发、极限编程都非常强调测试的重要性。

第7章 容器与数据结构
容器是STL中最引人注意的部分,vector,deque, list, set和map分别实现了最常用的
动态数组(向量)、双端队列、双向链表、集合和映射五种数据结构,它们以泛型的
形式提供,可以容纳任意的类型(但也有例外,比如auto_prt)。

Boost程序库基于STL同样的设计理念,实现了STL没有来得及实现的新型容器,如散列
容器、循环队列、多维数组。

本章总共介绍10个容器(数据结构),首先是array,dynamic_bitset, unordered,
bimap和circular_buffer,它们都是对STL原有容器在概念上的扩展,与STL容器的接口
非常相似。随后是tuple,any,variant,它们三个既是数据结构也是特殊的容器,展示
了泛型编程的精妙用法。最后介绍新型的容器multi_array和property_tree。

这些容器都对容纳的元素类型有一个基本要求:析构函数不能抛出异常,有的容器
还要求元素具有缺省构造函数或者拷贝构造函数。

7.1 array
array包装了C++语言内建的数组,为基提供标准的STL容器接口,如begin(), front()等。
array已经被放入C++11的新特性中了。

成员函数at()类似operator[],根据索引值访问内部元素,但at()有范围检查的功能,
如果索引值超过容器大小会抛出异常。

array的data()和c_array()以C数组的形式返回内部数组指针,用于需要原始指针的场合
两者的功能基本相同,但data()在需要的时候可以返回一个数组的常量指针,不允许
变动容器元素。

为了方便操作array对象,array还提供了operator==,swap()和assign()函数:
operator=使用std::copy算法实现了赋值操作;
swap()使用boost::swap可以高效地交换两个array对象
assign()使用标准算法std::fill_n将容器内所有元素赋值为value。

此外array还重载了==, <, >等比较操作符,使得字典序比较两个array对象。(但没有
使用operators库来简化操作符重载定义)。

7.1.3 用法
array需要在模板参数列表中指明数组的类型和大小,声明的形式与vector和普通数组
都很相像,它的使用方法与普通数组没有太大的区别。

基本上,读者可以把array看做是普通数组,只是多了一些STL风格的成员函数,可以
方便地搭配STL算法,是一个“更好的”数组。

7.1.4 能力限制
array的能力缺陷主要是:
没有构造函数,不能指定大小的初始值(只能使用模板参数指定大小);
没有push_back()和push_front(),因为它不能动态增长。
不能搭配插入迭代器适配器功能,同样因为它不能动态增长。

因此,array的功能相当有限,只能应用在已知数组大小,如果需要可动态变动的容量
的数组,请使得std::vector或boost::scoped_array。

7.1.5 array的初始化
虽然arrray没有构造函数,但它可以使用与普通数组一样的风格进行初始化,把数组元素
以逗号分隔放进花括号中,例如:
array<string, 3> ar = {"alice", "bob", "carl"};
也可以在花括号中初始化部分元素,剩余的元素将调用缺省构造函数完成初始化。

array也可以与assign库的list_of配合工作。但因为没有insert()、push_back()、
push_front()等成员函数,不能对它调用这些同名的assign库函数。
如:
array<int, 3> arr(list_of(2)(4)(6)); // 只能用list_of

7.2 dynamic_bitset
C++98标准为处理二进制数值提供了两个工具:vector<bool>和bitset。
vector<bool>是对元素类型为bool的vector特化,它内部并不真正存储 bool值,而是
以bit来压缩保存、使用代理技术来操作bit,造成的后果就是它很像容器。大多数
情况下的行为与标准容器一致,但它不是容器,不满足容器的定义。

bitset与vector<bool>类似,同样存储二进制位,但它的大小固定,而且比vector<bool>
支持更多的位运算。

vector<bool>可以动态增长,但不能方便地进行位运算;bitset则正好相反,可以方便
地对容纳的二进制位做位运算,但不能动态增长。

boost.dynamic_bitset的出现恰好填补了这两者之间的空白,它类似标准库的bitset,
同时长度又是动态可变的。

7.3.2 散列集合的用法
hash_set/unordered_set具有与std::set相同的功能。可以用size()获得容器大小,用
empty()判断空,支持比较操作符,可以用count()、find(),大多数应用std::set的
场景都能用hash_set/unordered_set替换。

我们唯一需要注意的是散列容器的无序性,我们不能在散列容器上使用binary_search、
lower_bound和upper_bound这样用于已序区间的算法,散列容器自再也不提供这样的
成员函数。

7.3.3 散列映射简介
unordered库提供两个散列映射类unordered_map和unordered_multimap。
它们的接口、用法与STL里的标准关联容器map/multimap相同,只是内部使用散列表代替
了二叉树实现、模板参数多了散列计算函数,比较谓词使用equal_to<>。

7.3.4 散列映射的用法
unordered_map/hash_map属于关联式容器,采用std::pair保存key-value形式的数据。
提供operator[]重载,用法与标准容器map完全相同。

unordered_multimap用法与之类似,但因为它允许有重复的key-value映射,因此不
提供operator[]。

7.3.5 性能比较
一般情况下散列容器的性能表现要比标准容器更好。

内部数据结构
unordered库使用“桶”(bucket)来存储元素,散列值相同的元素被放入同一个桶中。
当前散列容器的桶的数量可以用成员函数bucket_count()来获得,bucket_size()返回
桶中的元素数量。

为了提高散列容器的性能,unordered库会在插入元素时自动增加桶的数量。用户不
能直接指定桶的数量(由散列容器自己管理会更好),但可以在构造函数或者rehash()
函数指定最小的桶的数量。

支持自定义类型
unordered库支持C++内建类型和大多数标准库容器,但不支持用户自定义的类型,因为
它无法计算自定义类型的散列值。

如果要使用unordered支持自定义类型,需要定制类模板的第二个和第三个参数,也就
是提供散列函数和相等比较谓词。

boost.unordered基本上是依据C++0X标准草案来实现的,它有个小的扩充,增加了比较
操作符operator==和operator!=,可以比较两个容器包含的元素是否相等。

7.4 bimap
C++标准提供了映射型容器map和multimap,它们就像是一个关联数组,把一个元素key
映射到另一个元素value,但这种映射关系是单向的,只能是key到value,而不能反过
来。

boost.bimap扩展了标准库的映射型容器,提供双向映射的能力,功能强大,其接口被特
特意设计为符合STL规范。

名字空间boost::bimaps,但被using语句引入到了名字空间boost。头文件
<boost/bimap.hpp>

7.4.3 基本用法
bimap可以容纳两个类型的元素,是这两种元素关系的集合,这是bimap的基本视图。此
外,这个关系还可以有两个视图:左视图和右视图,分别用两个成员变量left和right
访问。相当于两个不同方向的std::map。其用法也同std::map一样。

对于bimap<X,Y>, bimap.left相当于map<X,Y>, bimap.right相当于map<Y,X>。

因为它的双向性,key/value值对必须都是唯一的,插入任何一个重复的值都将是无效
操作。

其次,bimap的两个视图的迭代器返回的值都是常量对象。相当于pair<const, const>。
不能如std::map那样修改value的值。

最后一点区别是bimap不能使用operator[]和at().

set_of  有序且唯一,相当于map
multiset_of, 有序,相当于multimap
unordered_set_of  无序且唯一,相当于unordered_map
unordered_multiset_of:
list_of
vector_of
unconstrained_set_of:

7.4.6 使用assign库
bimap的接口兼容STL,因此可以很容易地使用assign库为它赋初值。
typedef bimap<multiset_of<int>, vector_of<string> > bm_t;
那么可以直接使用assign::list_of为它初始化:
bm_t bm = assign::list_of<bm_t::relation>(1, "111")(2, "222");

bm的左视图是个multiset,可以使用assign库的insert()函数赋值,这里不需要使用
assign名字空间限定。
insert(bm.left)(3, "333")(4, "444");

bm的左视图是个vector,可以使用push_back()函数赋值,如果右视图是list_of定义的,
那么还可以使用push_front()函数。
push_back(bm.right)("555", 5)("666", 6);

7.4.7 查找与替换
当bimap的左右视图是set时,可以如std::map一样调用成员函数find(),以键值这索引
查找元素。

7.4.7 查找与替换
当bimap的左右视图是set时,可以如std::map一样调用成员函数find(),以键值为索引
查找元素。

bimap还通过视图提供std::map所没有的替换功能,可以直接修改key或者value。
视图的成员函数replace_key()可以替换键,replace_data()可以替换value,它们接受
一个指示位置的迭代器和值作为参数。返回bool表示替换是否成功。

除了使用replace_key()和replace_data()函数,bimap还使用了boost.lambda库提供更
快速的修改函数modify_key()和modify_data()。如果因为集合类型的约束导致修改失败
将会删除修改的元素,而不是抛出异常通知用户,所以在使用modify时要小心。

7.4.8 投射
bimap提供三个成员函数project_left()、project_right()和project_up(),可以把
bimap的迭代器分别投射到左视图、右视图和关系视图上。这允许用户以一种视图查找
然后很容易在转换到其他视图再进行其他操作。

7.5 circular_buffer
circular_buffer实现了循环缓冲区的的数据结构,支持标准的容器操作如push_back,
但大小是固定的,当到达容器末尾时将自动循环利用容器另一端的空间。

7.5.2 用法
circular_buffer被设计成一种可以与STL共同使用的容器,实现了一个大小固定的循环
队列,就像是一个deque和stack。可以像普通双端队列一样执行push_back()、
push_front()、insert()等操作来增加元素,也可以像栈一样用pop_back()、
pop_front()来弹出元素。

7.5.4 空间优化型缓冲区
circular_buffer在创建时一次性分配所需的内存,这是标准容器的通常做法,但对于
循环缓冲区数据结构可能不一定最合适。因此,circular_buffer库还提供了一个
circular_buffer_space_optimized类,它是circular_buffer的适配器,只有在确实
需要时才分配内存空间,而且当容器内元素减少时也会自动释放内存。

当程序需要容纳大量的元素,但多数情况下仅保存较少数量元素的时候就可以使用
circular_buffer_space_optimized,它可以有效地减少内存的占用。

7.6 tuple
tuple(元组)定义了一个有固定数目元素的容器,其中的每个元素类型都可以不相同,
它是std::pair的泛化,可以从函数返回任意数量的值,也可以代替struct组合数据。

7.6.1 最简单的tuple:pair
标准库中提供的模板类std::pair是tuple的特例。

tuple要求元素类型支持缺省构造或者赋值操作。
如果tuple的元素类型是引用,那么在初始化时必须给予初值。

make_tuple
为了方便创建tuple对象,tuple库提供与make_pair()类似的make_tuple()函数,

如果想让make_tupe生产的tuple数据类型是引用,那么要用到ref库的ref()和cref()函
数,它们可以包装变量,使tuple的类型为T&或者从const T&,这在用于不可拷贝的数据
类型时是很有用的。

tuple<const int&, string &> t2 = make_tuple(cref(i), ref(s));

7.6.4 访问元素
tuple提供成员函数get<>()访问内部的值。它是一个模板函数。
例如:
BOOST_AUTO(t, make_tuple(1, "char[]", 100.0));
assert(t.get<0>() == 1);
cout<<++t.get<0>();
因为get<>()是一个模板函数,所以模板实参<int N>必须是一个编译期可确定的,除
了成员函数get<>(),tuple库还提供一个自由函数boost::get<>(),它的用法与同名
成员函数完全相同,只是需要接受一个tuple变量作为操作的对象。例如:
get<0>(t);

7.6.4 比较操作
它将比较操作符转发到内部的各个元素进行比较,因此要求tuple的元素必须能够执行
比较操作,作为比较对象的两个tuple也必须要相同的元素个数,不则也会引发编译错误

7.6.6 输入输出
对tuple内的所有元素逐个调用operator<<或operator>>,因此要求每个元素都支持输
入输出。输入输出格式默认是:整个tuple用圆括号包围,元素值间用空格分隔。

7.6.7 连续变量
tuple库提供一个类似make_tuple()的函数tie()。它可以把变量连结到tuple上,生成
一个元素类型全是引用的tuple。相当于make_tuple(ref(a), ref(b),...),可以被用于
左值。
因此可以对tuple执行“解包操作”,这在接收返回tuple的函数返回值时特别有用。

它还可以应用于std::pair:
int i, string s;
tie(i, s) = make_pair(200l, "adc");

关于tie()还有一个特殊的对象tuples::ignore,它相当于一个点位符,可以对赋值时
“忽略”某些对象。

7.6.8 应用于assign库
assign库提供了一个初始化工具tuple_list_of,它可以初始化元素类型为tuple的容器
用法类似于map_list_of和pair_list_of。例如:
typedef tuple<int, double, string> my_tuple;
using namespace boost::assign;

vector<my_tuple> v = tuple_list_of(1, 1.0, "123")(2, 2.0, "456");
其他的assign库操作函数需要使用make_tuple()或者tie()函数来创建可插入容器的
tuple对象。例如:
v += make_tuple(3, 3.0, "789"), make_tuple(4, 4.0, "abc");

我们无法使用循环来处理tuple,可以使用递归方法。

7.7 any
any不是一个模板类
但它的构造函数any(const ValueType &)和赋值函数operator=(const ValueType&)
是模板函数,可以接受任意的类型,将值存入内部的一个模板类holder。所以any实际
上可以说是一个包装类。

any不会对指针执行delete操作,因此,如果应使用any来保存智能指针。

7.7.2
any的类接口很小很简单,它的出现让C++的强类型语法检查失去了作用,C++仿佛变成了
一种弱类型的动态语言。
它可以被初始化或者赋值任意类型的数据。

在any中存储字符串的时候我们必须用std::string,如果直接使用C字符串会引发编译
错误:这是因为C字符串是一个普通数组,它没有拷贝构造函数,不符合any对类型的
要求。

但我们在用any_cast()取回存入的值的时候,我们必须知道any内部值的确切类型才能
调用成功。

在用any中保存指针时要特别小心,因为空指针对于any也是有效的值,在用any_cast取
出来之后必须要进行测试。例如:
string *ps = new string("abc");
a = ps;
if (!a.empty() && any_cast<string*>(a))
{ cout<<*any_cast<string*>(a)<<endl;}
但这样的代码很麻烦,而且还有内存泄漏的危险,所以除非必要,应该使用智能指针
包装原始指针再放入any。

7.7.7 应用于容器
当容器的元素类型是any时,容器的表现就像是一个可持有多个不同类型对象的动态
tuple。
也可以对any容器使用assign库来初始化或赋值:例如:
vector<any> v2 = list_of<any>(10)(0.644)(string("char"));

如果希望一种数据结构具有tuple那样容纳任意类型的能力,又可以在运行时动态变化
大小,那么我们可以用any作为元素类型搭配容器。

7.8 variant
variant与any有些类似,是一种可变类型,是对C/C++中union概念的增强和扩展。
普通的union只能持有POD(普通数据类型),而不能持有如string,vector等复杂类型,
variant则没有这个限制。

variant提供了一个外界自由函数get()来访问内部元素。这是因为variant的设计出发
点与any不同,它的目的是存储多个数据的联合,而不是任意类型的容器。

7.8.3 用法
variant必须要在模板参数中指定它所能容纳的类型。它的写法与tuple很相似。

7.9 multi_array
多维数组在C++98中除了原始数组,只能用vector<vector<T> >来代替,但很不方便。
multi_array库解决了这个问题,它是一个多维容器,高效地实现了STL风格的多维数组,
比使用原始多维数组或者vector of vector更好。

7.9.3 用法
multi_array的模板参数很像标准容器,除了容纳的元素类型外,它多了一个维数的模板
参数,用来指定多维数组的维数。例如,下面代码:
multi_array<int, 3> ma; // 相当于int ma[X][Y][Z];
下面代码声明了一个维度为2/3/4的三维数组:
multi_array<int, 3> ma(extentsp[2][3][4]);
multi_array的总维数可以用成员函数num_dimensions()获得,它的返回值就是模板参数
中NumDims。函数shape()返回一个常量指针(数组),里面有NumDims个元素,表明了具
体各个维度的值。

multi_array重载了operator[],可以像普通数组那样访问内部的元素,operator[]在
debug模式下执行范围检查,如果访问索引超过了多维数组的范围会引发BOOST_ASSERT
断言失败从而退出程序。但在release模式下的行为则未定义。

multi_array另外提供一个data()成员函数,以指针的形式返回内部的数组,它包含了所
有的元素,成员函数num_elements()可以获得元素的总数。

7.9.4 改变形状和大小
multi_array可以在运行时使用成员函数reshape()改变多维数组的形状,即变动各个维
度的大小,但总维数的元素数量保持不变,变动前的维度乘积与变动后的维度乘积必须
相同。

reshape()接受一个维度序列作为参数,std::vector或者boost::array都可以,能常
array会更快。

例如,把一个三维数组由[2][3][4]变成[4][2][3]可以这样:
multi_array<int,3> ma(extents[2][3][4]);
assert(ma.shape()[2] == 2);
array(std::size_t, 3> arr = {4,3,2};
ma.reshape(arr);
ma.reshape(arr);
assert(ma.shape()[4] == 4);

multi_array也可以在运行时使用成员函数resize()改变多维数组的大小,动态增长或
缩减元素的数量,但总维数不能变(它已经在模板参数中固定了)。resize()函数既
可以接受维度序列,也可以接受extents表达式。

在变动大小时multi_array将重新分配内存,原有的元素将被复制到新内存,然后被
析构,新增的元素使用缺省构造函数构造。如果multi_array的元素总数变少,那么原
有的多余元素将被删除。

7.9.5 创建子视图
多维数组的操作是比较复杂的,multi_array库允许用户为多维数组创建一个只查看
其中一部分数据的子视图view,子视图既可以与原数组拥有相同的维数,也可以少于
原来的维数。后一种情况被称为多维数组的“切片”(slice),它可以降低数组的维数,
使之更容易处理。

我们需要使用index_gen类的预定义实例indices来定义子视图的索引范围。
indices用法类似extents对象,重载了operator[]来限定范围,它的参数是一个
multi_array<T,N>::index_range对象,它用来指定从多维数组中抽取的维度范围。

而multi_array的operator[]接受indices对象返回子视图,子视图的类型是
multi_array<T, N>::array_view<N>::type。
获取一个2*2的子视图可以是这样的:
BOOST_AUTO(view, ma[indices[range(0,2)][range(0,2)]]);

子视图也是一个multi_array,拥有相同的接口,可以用operator[]、num_dimensions()
等成员函数操作,但不能改变子视图的形状的大小。

7.9.6 适配普通数组
multi_array是多维数组最常用的类,它自己管理内存,可以动态增长,但有的时候我们
需要将一个一维数组适配成多维数组进行处理。
multi_array库提供了另外两个类multi_array_ref和const_multi_array_ref来满足这
一需求。它们可以把一段连续的内存(原始数组)适配成多维数组,用法类似
smart_ptr库中的scope_array。

multi_array_ref和const_multi_array_ref都通过构造函数接受一块外界的内存,把它
适配成多维数组的接口,适配后的用法除了不能动态增长外与multi_array完全相同,
包括operator[]访问和reshape()改变维度。const_multi_array_ref的功能要更少一些
它是只读的,适配后不能修改数组元素的值。
例如:
int arr[12];
multi_arrray_ref<int, 2> ma(arr, extents[3][4]);

用序列指针维数
除了使用extents对象,multi_array还支持另一种创建对象的方法,直接向构造函数
传入一个维度序列,例如:
array<std::size_t, 3> arr = {2, 3, 4};
multi_array<int, 3> ma(arr);
这里必须使用array或者vector,通常array会更快。

我们不能直接使用assign库的list_of,因为list_of生成的匿名数组不满足multi_array
的序列概念要求,但可以把array或vector和list_of组合起来使用,这样就可以在一
句话中完成multi_arrray的构造,例如:
multi_array<int, 3> ma(vector<int>(assign::list_of(2)(2)));

7.10 property_tree
property_tree是一个保存了多个属性值的树形数据结构,可以用类似路径的简单方式
访问任意节点的属性,而且每个节点都可以用类似STL的风格遍历子节点。property_tree
特别适合于应用程序的配置数据处理,可以解析xml, ini, json和info四种格式的文本
数据。

property_tree库的核心类是basic_ptree。
basic_ptree的接口很像标准容器std::list,可以执行很多基本的元素操作,如使用
begin()和end()遍历当前属性树的所有子节点。此外它还增加了操作属性树的get()、
get_child()、get_value()、data()等额外的操作。

basic_ptree有两个重要的内部类型定义self_type和value_type。self_type是
basic_ptree模板实例化后自身的类型,它也是子节点的类型。value_type是节点的
数据结构,它是一个std::pair,含有节点的属性名(first)和节点自身(second)。
同标准字符串类basic_string一样,通常我们不直接使用basic_ptree,而是使用预定
义好的typedef:ptree、wptree、iptree、wiptree,这些名字中的前缀i表示忽略大小
写,前缀w表示支持宽字符。

第8章 本章介绍了两个Boost算法库:foreach和minmax

第9章 数学与数字
C++98涵盖了C89所定义的数学运算函数,例如sqrt()、abs()、log()等等,标准库还提
供了复数complex和数值序列valarray,但对于近代数学来说这些工具还很不够用。

Boost库对标准库进行了补充,增加了C99标准中引入的大量特殊数学函数和复数函数,
还有四元数、八元数、拉格朗日多项式、贝塞尔曲线等许多扩充,几乎覆盖了近现代
数学的大部分领域,包括高等代数、线性代数和统计学。
本章介绍Boost数学领域的四个库:integer、rational、crc和random。

9.1.1 integer_traits
C++98在标准头文件<limits>里定义了模板类std::numeric_limits<>,它使用模板特化
技术给出了int,double等数据类型的相关特性,如最大最小值、是否有符号等等。这些
特性大部分是编译期的常量,但最大最小值函数却不是常量,只能在运行时使用,这在
泛型编程时会带来一些不方便。

<boost/cstdint.hpp>还有两个最大宽度整数类型:intmax_t和uintmax_t有来表示编译
器支持的最大整数,可以兼容其他任意整数类型。

9.1.3 整数类型模板类
头文件<boost/integer.hpp>与<boost/cstdint.hpp>功能类似,也提供一系列的整数
类型定义,但它不使用typedef的形式,而是用模板类,可以自动地选择最合适的整数
类型。

<boost/integer.hpp>包含两组模板类。最容易使用的模板类是int_fast_t<>,它的
声明如下:
例如:
int_fast_t<char>::fast a; //char类型的最快类型
cout<<typeid(a).name()<<endl;

整数类型模板类与普通类型一样容易使用,只需要多写一点点模板类代码,它就会根据
所需要的整数宽度或者处理的整数值自动为你选择最合适的整数类型。
由于整数类型模板类内仅有typedef,没有其他数据成员或成员函数,因此这些类不会
产生任何运行时开销,与使用内置整数类型或者Boost整数类型同样高效。
示例代码:

#include <boost/integer.hpp>
#include <boost/format.hpp>
#include <typeinfo>

int main()
{
format fmt("type:%s, size=%dbit\n"); //一个format对象
uint_t<15>::fast a; //可容纳15位的无符号最快整数
cout<<fmt%typeid(a).name%(sizeof(a)*8);
int_max_value_t<32700>::fast c; // 可处理32700的最快整数
cout<<fmt%typeid(c).name()%(sizeof(c)*8);

9.2 rational
科学计算的基础是数字运算,C++语言内建了int和float/double,分别用于支持有
限精度的整数和实数(小数).C++98标准库提供了complex,支持复数的概念。

9.2.2 创建与赋值
rational有三种形式的构造函数,缺省构造函数(无参)创建一个值为零的有理数,单
参的构造函数创建一个整数,双参数的构造函数创建一个经规范化的分数。
rational重载了operator=,可以直接从另一个整数赋值,也可以使用成员函数assign()
接受分子/分母形式的两个整数参数赋值。
但ration不能多浮点数(float/double)构造或者赋值。

除了bool以外,ratinal不能隐式转换到其他任何类型(即使是分母为1的有理数转换到
整数),而必须使用ration_cast<>函数,这个函数模仿了标准库的转型操作符,可以
显式地转换到其他数字类型。

ration重载了流输入输出操作符,可以直接与IO流配合工作,格式为用斜杠分隔两个
整数。

9.2.6 分子与分母
rational的成员函数numerator()和denominator()可以直接访问有理数对象的分子和
分母,但返回的是一个右值,只能读而不可写

9.2.7 与数学函数配合工作
rational没有实现到float/double的隐式转换,故不能直接使用sqrt(),pow()等标准C
函数,但取绝对值的abs()函数是一个例外,因为rational实现了对它的重载。我们必
须使用ration_cast<>转型成float或者double值才可以。

9.2.11 最大公约数的最小公倍数
rational库还提供了两个工具函数:最大公约数gcd()和最小公倍数lcm()。这两个函数
实际是另一个库math()的组成部分,位于名字空间boost::math。被rational库引入了
名字空间boost。

9.3 CRC
CRC(循环冗余校验码)是一种被广泛应用的错误验证机制,它使用一定的规则处理数据,
计算出一个校验和,并附在数据末尾发送给接收方。接收方使用同样的规则计算CRC,
如果两个计算结果一致则说明传输正确,否则表明传输过程发生了错误。

CRC库提供了两个类用于计算循环冗余校验码:一个是crc_basic,以bit为单位进行计算,
速度慢,仅供理论研究;另一个是crc_optimal,是优化过的处理机,以byte字节为单位
进行计算,速度很快,适合于实际应用,crc库的所有实现均基于crc_optimal。

9.3.2 预定义的实现类
crc_optimal类的模板参数过多,实际使用时很不方便,因此crc库基于crc_optimal预
定义了四个实现类:crc_16_type、crc_ccitt_type、crc_xmodem_type和crc_32_type.
这四个类代表了历史上被广泛使用的CRC计算方法,前三个是16位的CRC码,第四个是
32位的CRC码。

较常用的是crc_32_type,它使用的算法被用于PKZip计算CRC值。定义如下:
typedef crc_optimal<32, 0x04C11DB7,0xFFFFFFFF,0xFFFFFFFF,true, true>
crc_32_type;

9.3.3 计算CRC
为了优化速度,crc_optimal只能以byte为单位处理数据,crc_basic可以处理bit.
成员函数process_byte()一次只能处理一个字节。process_bytes()和process_block()
函数则可以处理任意长度的数据块,两者的输入参数不同,process_bytes()用数据块
+数据块长度的形式,而process_block()接受数据块的开始和结束指针。
在任何时候crc_optimal都可以调用checksum()函数获得当前已计算出的CRC值,或者调
用reset()重置计算结果。

crc_optimal还重载了括号操作符operator(),这有两种用法。不带参数的形式直接返回
CRC值,相当于调用checksum(),带参数的形式接受一个字节,相当于调用process_byte()
因此可以把crc_optimal对象当作函数对象传递给STL算法。
例如:
crc_32_type crc32;
string str = "123456789";
crc32.reset();
//把crc对象作为函数对象传递给for_each算法处理字符串
cout<<for_each(str.begin(), str.end(), crc32)()<<endl;
cout<<crc32()<<endl;

for_each函数存储的函数对象的副本。

9.3.4 CRC函数
crc_optimal模板类及其预定义实现类可以非常容易地计算CRC值,但crc库还提供了两个
自由函数:crc()和augmented_crc()。

crc()函数使用模板参数在内部构造crc_optimal对象,直接计算CRC值并立即返回,例如
crc<16, 0x8005,0,0,true, true>("1234567890", 10);
等同于crc_16_type().process_bytes("1234567890", 10);
augmented_crc()的用法与crc()类似,只需要传递较少的参数,但它的计算规则与crc()
和crc_optimal模板类有所不同,主要是为了方便计算附加crc码的数据。

9.4 random
random库专注于伪随机数的实现,有多种算法可以产生高质量的伪随机数,并提供随机
数发生器、分布等很多有用的数学、统计学相关概念。

9.4.1伪随机数发生器
random库提供了26个伪随机数发生器,使用的算法包括线性同余算法、逆同余算法、
Mersenne Twister算法、fibonacci算法、ranlux算法及它们的混合。

在这里作者推荐三个伪随机数类:rand48、mt19937和lagged_fibonacci19937,它们产
生随机数的速度由高到低,产生的随机数质量和所需内存则低到高。

虽然这些随机数发生器使用的算法不同,实现也有较大的差异,但基本接口是类似的,
像这样:
class random_demo
{
random_demo(int 0x) //使用种子值的构造函数
int seed(int x0); //设置种子值
int operator()(); // 调用操作符,产生随机器
};

9.4 伪随机数发生器的构造
伪随机数发生器在程序中应尽量少发生构造操作,最好是实现为单件。原因有两个,一是
伪随机器发生器的构造成本相当昂贵,越是高质量的伪随机数发生器越需要初始化大量
的内部状态。二是由于构造发生器提供的种子一般是使用系统时间,如果两个发生器
构造的时间间隔很短,那很可能种子值是相同的,从而得到两个行为相同的伪随机数
发生器,令随机器失去意义。

9.4.4 随机数分布器
伪随机数发生器能够产生高质量的随机数,但产生的随机数都是整数类型,均匀分布在
整数域。
random库为些提出了随机数分布器的概念,把伪随机数发生器产生的整数域随机数映射
到另一种分布,提高了代码的复用性。
random库提供了15个分布的映射类:
uniform_smallint: 在小整数域内的均匀分布
uniform_int: 在整数域上的均匀分布
uniform_01: 在区间[0,1]上的实数连接均匀分布
uniform_real: 在区间[min, max]上的实数连续均匀分布
bernoulli_distribution: 伯努利分布
binomial_distribution: 二项分布
cauchy_distribution: 柯西(洛伦兹)分布
gamma_distribution: 伽马分布
poisson_distribution: 泊松分布
geometric_distribution: 几何分布
triangle_distribution: 三角分布
exponential_distribution: 指数分布
normal_distribution: 正态分布
lognormal_distribution: 对数正态分布
uniform_on_sphere: 球面均匀分布

9.4.6 随机数分布器用法
随机器分布器必须搭配随机数发生器才能工作,因此必须先定义一个随机数发生器作为
随机数源,然后再选择恰当的模板参数定义一个分布器(如uniform_smallint<>),把随
机数映射到某个区间的分布,最后调用分布器的operator()获得所需的随机数。

9.4.7 变量发生器(随机数引擎)
有了随机数发生器和分布器,已经基本可以解决大部分与随机数相关的问题了。但为了
方便使用,random库还提供了一个模板类variate_generator<>(变量发生器),组合了
随机数发生器和分布器,使产生随机数更加容易便捷。

variate_generator的模板参数是随机数发生器Engine和分布器Distribution,Engine
的类型可以是T(原始类型)、T*(指针类型)或者T&(引用类型),Distribution则必
须是T(原始类型),不能是指针或引用。

variate_generator在构造函数中指定随机数发生器和分布器的实例,可以用engine(),
distribution()获得随机数发生器和分布器的引用,operator()产生随机数。

variate_generator也是可以拷贝和赋值的,这意味着它同伪随机数发生器一样可以保存
状态以备后用,但如果模板参数的Engine是引用类型则无法拷贝、赋值。

variate_generator必须组合随机数发生器和分布器。

C++11库提供了引用包装器reference_wrapper<T>。

第10章 操作系统相关
Boost提供了数个操作系统相关的库,其中的两个库system和filesystem即将成为C++
将标准的一部分。

io_state_savers库,它可以保存输入输出流的状态并自动恢复;第二个是system库,
第三个是filesystem库,提供跨平台的文件系统处理能力。最后是program_options库
它提供了强大的命令行参数解析和程序运行选项配置。

本章的库除io_state_savers外都需要编译,这是由其平台相关特性所决定的。

ios_savers有两个内部类型定义:state_type和aspect_type,它们标记了ios_saver的基
本属性:
state_type是ios_saver要保存的流类型,如ios_base、basic_ios<>;
aspect_type是ios_saver要保存的具体流状态,如fmtflags、iostate.

ios_savers的构造函数接受一个流对象的引用,同时保存该流的aspect_type值。另一种
形式的构造函数则在保存的同时变更流的状态为new_value。
ios_saver在析构时会自动把保存的状态恢复到流对象。我们也可以在任何时刻使用
restore()函数手工恢复流状态。

ios_saver把赋值操作符operator=声明为私有的,因此都是不可赋值的,但允许拷贝
构造,可以在程序中拷贝一个ios_saver以备后用。

10.1.2 用法
io_state_savers库里有很多用于保存并恢复流状态的类,它们被分成四组,分别是:
基本的标准属性保存器,如ios_flags_saver、ios_width_saver;
增强的标准属性保存器,如ios_iostate_saver、ios_rdbuf_saver;
自定义的属性保存器, 如ios_iword_saver、ios_pword_saver;
组合的属性保存器,如ios_all_saver。

最简单也是最常用的类是ios_all_saver和wios_ass_saver,它们可以保存流所有的状态
wios_all_saver是ios_all_saver的宽字符形式,用来配合宽字符wcout使用。

我们可以使用流的重定向功能,把日志的输出转到一个文件流中,
但下面代码将发生崩溃的错误:
int main()
{
string filename("c:/test.txt");
cout<<"log start"<<endl;
if(!filename.empty())
{
ofstream fs(filename.c_str()); // 打开一个文件流
cout.rdbuf(fs.rdbuf()); // 标准输出重定向到文件流
logging("fatal msg"); // 重定向输出日志
} // 文件流被自动析构
cout<<"log finish"<<endl; // cout无法输出,运行错误
}
出错的原因在于流的重定向。当文件流fs被设置为cout的缓冲区后,cout将总向它输出
数据。但fs是一个局部变量,当离开if语句的作用域后它被自动销毁,导致缓冲区失效
但cout对此并不知情,仍然向一个无效缓冲区写入数据,从而发生了严重的运行错误。

是修复这个bug非常容易,只需要在重定向或任何可能改变流状态的操作前用
ios_all_saver保存cout的状态。那么在离开作用域时它就会自动把cout恢复到最初的
状态。如下所示:
ios_all_saver ifs(cout); // 保存流的所有状态
cout.rdbuf(fs.rdbuf());
// 离开作用域,导致保存器析构,自动恢复流的状态。

10.2 system库
system库使用轻量级的对象封装了操作系统底层的错误代码和错误信息,它作为基础部
件已经被filesystem和asio等库调用,并且被接受为C++0x TR2.

10.3 filesystem
目录、文件处理是脚本语言如shell、python、perl所擅长的领域,但C++语言缺乏对
操作系统中文件的查询和操作能力,因此C++程序员经常需要再掌握另外一门脚本语言
以方便自己的工作。

filesystem库需要system库支持,因此必须先编译system库。
filesystem库需要编译才能使用。
如果使用嵌入源码的方式,需要在包含头文件之前定义宏BOOST_FILESYSTEM_NO_LIB、
BOOST_SYSTEM_NO_LIB或者直接使用BOOST_ALL_NO_LIB。

filesystem库的核心类是basic_path,它屏蔽了不同文件系统的差异,使用可移植的
POSIX语法提供了通用的目录、路径表示,并且支持POSIX的符号连接概念。

但通常我们不直接使用basic_path,而是使用预定义的typedef:path和wpath。

10.3.3 路径表示
path的构造函数可以接受C字符串和string,也可以是一个指定首末迭代器字符串序列
区间。path使用标准的POSIX语法提供可移植的路径表示,它也是unix、linux的原生
路径。POSIX语法使用斜杠/来分隔文件名和目录名,点号.表示当前目录,双点号表示
上一级目录。

path也支持操作系统的原生路径表示,比如在windows下使用盘符,分隔符使用反斜杠\
因上反斜杠被C++定义为转义符,在字符串中使用反斜杠表示路径的时候必须连续写\\
才能被识成一个\,因此这种方式通常没有unix风格的斜杠方式方便。

path重载了operator/=,可以像使用普通路径一样用/来追加路径,成员函数append()
也可以追加一个字符串序列。

自由函数system_complete()可以返回路径在当前文件系统中的完整路径(也就是通常
所说的绝对路径)。

10.3.4 可移植的文件名
filesystem库提供了一系列的文件名(或目录名)检查函数,可以根据系统的命名规则
判断一个文件名字符串的有效性。

自由函数portable_posix_name()和windows_name()分别检测文件名字符串是否符合
POSIX规范和Windows规范。

portable_name()判断名字是否是一个可移植的文件名,相当于portable_posix_name()
&&windows_name(),

filesystem库提供了个native()函数,它判断文件名是否符合本地文件系统命名规则,
在windows下它等同于windows_name(),而在其他操作系统下则只是简单地判断文件名
不是空格且不包含斜杠。

10.3.5 路径处理
path类提供了丰富的函数用于处理路径,可以获取文件名、目录名、判断文件属性等等,
path的成员函数string()返回标准格式的路径表示,directory_string()返回文件系统
格式的路径表示,parent_path()、stem()、filename()和extension()分别返回路径
中的父路径、不含扩展名的全路径名、文件名和扩展名。

is_complete()用于检测path是否是一个完整(绝对)路径,这需要依据具体的文件系统
的表示,例如在windows系统的完整路径需要包括盘符。

root_name()、root_directory()、root_path()这三个成员函数用于处理根目录,如果
path中含有根,那么它们分别返回根的名字、根目录和根路径。

relative_path()返回path的相对路径,相当于去掉了root_path()。

根路径的相对路径的这四个函数都有对应的has_xxx()的形式,用来判断是否存在对应的
路径。同样,has_filename()和has_parent_path()用于判断路径是否有文件名或者父
路径。

remove_filename()删除路径中最后的文件名,把path变为纯路径表示
replace_extension()可以变更文件的扩展名。

path类还提供迭代器begin()和end(),可以迭代路径中的字符串。

当然了,我们还可以使用boost中的字符串处理库,如sting_algo、xpressive。

我们通常应该使用下面两个异常类:
typedef basic_filesystem_error<path> filesystem_error;
typedef basic_filesystem_error<wpath> wfilesystem_error;

示例,下面的代码检查文件的大小,如果文件不存在,则抛出异常:
path p("c:/test.txt");
try
{
file_size(p);
}
catch(filesystem_error &e)
{
cout<<e.path1()<<endl;
cout<<e.what()<<endl;
}
由于文件系统位于程序之外,是不可控具全局共享的,因此访问目录或文件随时都有可
能发生异常,例如文件已经删除、文件已存在等。

应总使用try-catch块来保护文件访问代码。

10.3.7 文件状态
filesystem库提供一个文件状态类file_status及一组相关函数,用于检查文件的各种
属性,如是否存在、是否是目录、是否是符号链接等。

10.3.8 文件属性
函数initial_path()返回程序启动时(进行main()函数)的当前路径
函数current_path()返回当前路径。它和initial_path()返回的都是一个完整路径(绝
对路径);
函数file_size()以字节为单位返回文件的大小;
函数last_write_time()文件的最后修改时间,是一个std::time_t。
last_write_time()还可以额外接受一个time_t参数,修改文件的最后修改时间。就像是
使用linux下的touch命令。

这些函数都要求操作的文件必须存在,否则会抛出异常,file_size()还要求文件必须是
个普通文件is_regular_file(name) == true

此外,函数space()可以返回一个space_info结构,它表明了该路径下的磁盘空间分配
情况,space_info的结构的定义如下:
struct space_info
{
uintmax_t capacity;
uintmax_t free;
uintmax_t available;
};
space()函数可以这样使用:
const int GBYTES =  1024*1024*1024;
space_info si = space("d:/");
cout<<si.capacity/GBYTES<<endl;
cout<<si.available/GBYTES<<endl;
cout<<si.free/GBYTES<<endl;

10.3.9 文件操作
filesystem库基于path的路径表示提供了基本的文件操作函数,如创建目录
create_directory、文件改名rename、文件删除remove、文件拷贝copy_file等。。

remove只能一次删除一个目录或文件,remove_all可以递归删除。
create_directory只能创建一级目录,create_directories可以一次创建多级目录。

10.3.10 迭代目录
filesystem库使用basic_directory_iterator提供了迭代一个目录下所有文件的功能,
它预定义了两个typedef:directory_iterator和wdirectory_iterator。

directory_iterator用法有些类似string_algo库的find_iterator、split_iterator
或xpressive库的regex_token_iteraator,空的构造函数生成一个逾尾end迭代器,
传入path对象构造将开始一个迭代操作,返回调用operator++即可迭代目录下的所有
文件,例如:director_iterator end;
for(director_iterator pos("d:/boost/"; pos != end; ++pos)
{
cout<<*pos<<endl;
}

注意,basic_director_iterator迭代器返回的对象并不是path,而是一个
basic_directory_entry对象,但basic_directory_entry类定义了一个到path的类型
转换函数,因此可以在需要path的语境中隐式转换到path.

basic_directory_entry可以用path()方法返回路径,status()返回路径的状态,
directory_iterator只能迭代本层目录,不支持深度遍历目录,可以使用递归来实现
这个功能。

由于递归遍历文件系统目录的功能非常有用也很有必要,因此filesystem库专门提供了
另一个类basic_recursive_directory_iterator,它遍历目录的效率要比递归的
directory_iterator高很多。

通常我们使用basic_recusive_directory_iterator的两个typedef:
recursive_directory_iterator和wrecurisive_directory_iterator。

recursive_directory_iterator能够迭代当前及子目录下的所有文件。
成员函数level()返回当前的目录深度m_level。每深入一层子目录则m_level增加,退出
时减少。成员函数pop()用于退出当前目录层次的遍历,同时--m_level。当迭代到一
个目录时,no_push()可以让目录不参与遍历,使用recursive_directory_iterator
和行为等价于directory_iterator。

第11章 函数与回调
首先我们学习两个小的工具类result_of和ref,result_of使用了复杂的技巧来自动推
导函数的返回值类型,ref可以包装对象的引用,在传递参数时消除对象拷贝的代价
或者将不可拷贝的对象变为可以拷贝。

bind是C++98标准库中函数适配器的增强,可以适配任意的可调用对象,包括函数指针、
函数引用和函数对象,把它们变成一个新的函数对象。function库则是对C++中函数
指针类型的增强,它能够容纳任意的可调用对象,可以配合bind使用。

最后是signals2库,它实现了威力强大的观察都模式。
如同C#的event/delegate或者java的observable/observer。

11、1 result_of

11.1.1 原理
所谓“调用表达式”,是指一个含有operator()的表达式,函数调用或函数对象调用
都可以称为调用表达式,而result_of可以确定这个表达式所返回的类型。
有点类似typeof库,typeof可以确定一个表达式的类型,但它不具备推演调用表达式
的能力。

result_of用到了很多C++的高级特性,如模板偏特化和SFINAE,并且部分依赖于编译
器的能力。它不仅可以用于函数指针,更重要的是用于函数对象进行泛型编程。

给定一个调用表达式,可以通过内部类型定义result_of<...>::type获得返回值的类型。

假设我们有一个类型Func,它可以是函数指针、函数引用或者成员函数指针,当然也可
以是函数对象类型,它的一个实例是func,Func有一个operator(),参数是(T1 t1,
T2 t2),这里T1,T2是两个模板类型,那么
result_of<Func(T1,T2)>::type
就是func(t1, t2)的返回值类型。

设类型Func可被调用(具有operator()),func是Func的一个左值,那么
typeid(result_of<Func(T1, T2...,TN)>::type)
必然等于
typeid(func(t1, t2, ...tN))。

11.2 ref
STL和Boost中的算法和函数大量使用了函数对象作为判断式或谓词参数,而这些参数
都是传值语义,算法或函数在内部保留函数对象的拷贝并使用。

一般情况下传值语义都是可行的,但也有很多特殊情况,作为参数的函数对象拷贝
代价过高(具有复杂的内部状态),或者不希望拷贝对象(内部状态不应该被改变),
甚至拷贝是不可行的(noncopyable、单件)。
boost.ref应用代理模式,引入对象引用的包装器概念解决了这个问题。

ref库定义了一个很小很简单的引用类型的包装器。名字叫reference_wrapper。
reference_wrapper的名字过长,声明引用包装对象很不方便,因而ref库提供了两个
便捷的工厂函数ref()和cref()。可以通过参数类型推导很容易地构造reference_
wrapper对象。
因为reference_wrapper支持拷贝,因此ref()和cref()可以直接用在需要拷贝语义
的函数参数中,而不专门使用一个reference_wrapper来暂存。例如:
double x = 2.0;
cout<<std::sqrt(ref(x))<<endl;

11.2.4 操作包装
ref库运用模板元编程技术提供两个特征类is_reference_wrapper和unwrap_reference
用于检测reference_wrapper对象:
is_reference_wrapper<T>的bool成员变量value可以判断T是否为一个reference_wrapper
unwrap_reference<T>的内部类型定义type表明了T的真实类型,无论它是否经过
reference_wrapper包装。

自由函数unwrapper_ref()为解开包装提供了简便的方法,它利用unwrap_reference<T>
直接解开reference_wrapper的包装,返回被包装对象的引用。

unwrap_ref()的这个功能很有用,可以把unwrap_ref()安全地用在泛型代码中,从而不
必关心对象的包装特性,总能够正确地操作对象。

struct square
{
typedef void result_type;
void operator()(int &x)
{
x = x*x;
}
};

int main()
{
typedef double(*pfunc)(double);
pfunc pf = sqrt;
cout<<ref(pf)(5.0); // 包装函数指针

square sq;
int x = 5;
ref(sq)(x);
cout<<x;

vector<int> v = (list_of(1), 2, 3, 4, 5);
for_each(v.begin(), v.end(), ref(sq));
}

11.3 bind
bind是C++98标准库中函数适配器bind1st/bind2nd的泛化和增强,可以适配任意的可
调用对象,包括函数指针、函数引用、成员函数指针和函数对象。bind远远地超越了
STL中的函数绑定器(bind1st/bind2nd),可以绑定最多9个函数参数,而且对被绑定
对象的要求很低,可以在没有result_type内部类型定义的情况下完成函数对象的绑定。

11.3.1工作原理
bind并不是一个单独的类或函数,而是非常庞大的家族,依据绑定的参数个数和要绑定
的调用对象类型,总共有数十个不同的形式,但它们的名字都叫做bind。

bind接受的第一个参数必须是一个可调用对象f,包括函数、函数指针、函数对象和成员
函数指针,之后bind接受最多九个参数,参数的数量必须与f的参数数量相等,这些参数
将被传递给f作为入参。

绑定完成之后,bind会返回一个函数对象,它内部保存了f的拷贝,具有operator(),
返回类型被自动推导为f的返回值类型。在发生调用时,这个函数对象将之前存储的
参数转发给f完成调用。

例如,如果有一个函数func,它的形式是:
func(a1, a2)
那么,它将等价于一个具有无参operator()的bind函数对象调用:
bind(func, a1, a2)()

bind的真正威力在于它的占位符,它们分别被定义为_1, _2, _3一直到_9,位于一个匿名
名字空间,占位符可以取代bind中参数的位置,在发生函数调用时才接受真正的参数。

占位符的名字表示它在调用式中的顺序,而在绑定表达式中没有顺序的要求,_1不一
定必须第一个出现,也不一定只出现一次。

注意,必须在绑定表达式中提供函数要求的所有参数,无论是真实参数还是占位符均
可以。占位符可以出现也可以不出现,出现的顺序和数量也没有限定。

bind完全可以代替标准库中的bind1st和bind2nd,使用bind(f,N,_1)和bind(f,_1,N)。

11.3.3 绑定成员函数
bind也可以绑定类的成员函数
类的成员函数不同于普通函数,因此成员函数指针不能直接调用operator(),它必须
被绑定到一个对象或者指针,然后才能得到this指针进而调用成员函数。因此bind
需要“牺牲”一个占位符的位置,要求用户提供一个类的实例、引用或者指针,通过
对象作为第一个参数来调用成员函数。
例如:
bind(&X::func, x, _1, _2,...);

注意,我们必须在成员函数前加上取地址操作符&,表明这是一个成员函数指针,否
则会无法通过编译,这是与绑定函数的一个小小的不同。

bind能够绑定成员函数,这样,它可以替代标准库中令人迷惑的mem_fun和mem_fun_ref
绑定器,用来配合标准算法操作容器中的对象。

下面的代码使用bind搭配标准算法for_each用调用容器中所有对象的print()函数:
for_each(v.begin(), v.end(), bind(&point::print, _1));
bind同样支持绑定虚拟成员函数,用法与非虚函数相同,虚函数的行为将由实际调用
发生时的实例来决定。

11.3.4 绑定成员变量
bind的另一个对类的操作是它可以绑定public成员变量,就像是一个选择器,用法
与绑定成员函数类似,只需要把成员变更名像一个成员函数一样去使用。

仍然以point类为例子,假设我们已经在vector中存储了大量的point对象,而我们想要
得到它们的x坐标值,那么bind可以这样使用:
transform(v.begin(), v.end(), v2.begin(), bind(&point::x, _1));
代码中bind(&point::x, _1)取出point对象的成员变量x。

使用bind,可以实现SGISTL/STLport中的非标准函数适配器select1st和select2nd的功
能,直接选择出pair对象的first和second成员。

11.3.5 bind绑定函数对象
bind不仅能够绑定函数和函数指针,也能够绑定任意的函数对象,包括标准库中的所有
预定义的函数对象。
如果函数对象有内部类型定义的result_type,那么bind可以自动推导出返回值类型,用
法与绑定普通函数一样。但如果函数对象没有定义result_type,则需要在绑定形式上
做一点改动,用模板参数指明返回类型。

标准库和Boost库中的大部分函数对象都具有result_type定义,因此不需要特别的形式
就可以直接使用bind。例如:
bind(plus<int>(), _1, _2);

对于自定义函数对象,如果没有result_type类型定义,例如:
struct f
{
int operator() (int a, int b)
{
return a+b;
}
};

那么我们必须指明bind的返回值类型,像这样。
cout<<bind<int>(f(), _1, _2)(10, 20)<<endl;

最在遵循规范为它们增加内部typedef result_type,这将使函数对象与许多其他标准
库和Boost库组件良好配合工作。

11.3.6 使用ref库
bind采用拷贝的方式存储绑定对象和参数,这意味着绑定表达式中的每个变量都会有一
份拷贝,如果函数对象或值参数很大、拷贝代价很高,或者无法拷贝,那么bind的使用
就会受到限制。
因此bind库可以搭配ref库使用。

11.4 function
function是一个函数对象的“容器”,概念上像是C/C++中函数指针类型的泛化,是一
种“智能函数指针”。它以对象的形式封装了原始的函数指针或函数对象,能够容纳
任意符合函数签名的可调用对象。因此,它可以被用于回调机制,暂时保管函数或
函数对象,在之后需要的时机再调用,使回调机制拥有更多的弹性。

function可以配合bind使用,存储bind表达式的结果,使bind可以被多次调用。

11.4.2 function的声明
function是一个模板类。
function使用result_of的方式,用类似函数声明的形式一次给出所有函数的类型信息,
这被称为“首选语法”。例如:
function<int()> func;
将声明一个可以容纳返回值为int、无参数函数的function对象。

11.4.3 操作函数
function的构造函数可以接受任意符合模板中声明的函数类型的可调用对象,如函数
指针和函数对象,也可以是另一个function对象的引用,之后在内部存储一份它的拷贝。

无参的构造函数或者传入空指针构造将创建一个空的function对象,不持有任何可调用
物,调用空的function对象将抛出bad_function_call异常,因此在使用function前
最好检测一下它的有效性。可以用empty()测试function是否为空,或用重载操作符
operator!来测试。它是类型安全的。

如果function存储的是函数对象,那么要求函数对象必须重载了operator==,是可比较
的。

两个function对象不能使得!=和==直接比较,这是特意的。因为function存在到bool的
隐式转换,function定义了两个function对象的operatorr==但没有实现,企图比较两
个function对象会导致编译错误。

11.4.5 用法
function就像是一个函数的容器,也可以把function想像成一个泛化的函数指针,只要
符合它声明中的函数类型,任何普通函数、成员函数、函数对象都可以存储在function
对象中,然后在任何需要的时候被调用。

function的这种能够容纳任意可调用对象的能力是非常重要的,在编写泛型代码的时候
尤其有用,它使我们可以接受任意的函数或函数对象,增加程序的灵活性。

存储成员函数时可以直接在function声明的函数签名式中指定类的类型,然后用bind
绑定成员函数:
function<int(demo_class&, int, int)> func1;
func1 = bind(&demo_class::add, _1, _2, _3);
demo_class sc;
cout<<func1(sc, 10, 20);

11.4.8 使用ref库
function使用拷贝语义保存参数。
function能够直接调用被ref库包装的函数对象,这个功能可以部分地弥补boost.ref
没有operator()的遗憾。

如果ref库不提供operator(),那么它将无法用于标准库算法,因为标准库算法总使用
拷贝语义,算法内部的改变不能影响原对象。

11.4 function
function是一个函数对象的“容器”,概念上像是C/C++中函数指针类型的泛化,是一种
”智能函数指针”,它以对象的形式封装了原始的函数指针或函数对象,能够容纳任意
符合函数签名的或调用对象。因此,它可以被用于回调机制,暂时保管函数或函数 
对象。

function可以配合bind或ref使用。

11.4.2 function的声明
function是一个模板类,function使用result_of的方式,用类似函数声明的形式一
次给出所有函数的类型信息,这被称为“首选语法”,例如:
function<int()> func;
将声明一个可以容纳返回值为int、无参函数的function对象。

11.4.3 操作函数
function的构造函数可以接受任意符合模板中声明的函数类型的可调用对象,如函数
指针和函数对象,也可以是另一个function对象的引用,之后在内部存储一份它的
拷贝。

无参的构造函数或者传入空指针构造将创建一个空的function对象,不持有任何可
调用物,调用空的function对象将抛出bad_function_call异常,因此在使用
function前最好检测一下它的有效性。可以用empty()测试function是否为空,或
者用重载操作符operator!来测试。function对象也可以在一个bool上下文中直接
测试它是否为空,它是类型安全的。

11.4.4 比较操作
function重载了比较操作符operator==和operator!=,可以与被包装的函数或函数
对象进行比较。如果function存储的是函数指针,那么比较相当于
function.target<Functor>() == func_pointer
例如:
function<int(int, int)> func(f);
assert(func == f);
如果function存储的是函数对象,那么要求函数对象必须重载了operator==,是
可比较的。
两个function对象不能使用==和!=直接比较,因为function存在到bool的隐式转换

function能够直接调用被ref库包装的函数对象,这个功能可以部分地弥补
boost.ref没有operator()的遗憾。

例如,下面代码定义了一个求总和的函数对象,它具有内部状态:
template<typename T>
struct summary
{
typedef void result_type;
T sum;
summary(T v = T()):sum(v){}
void operator()(T const &x)
{ sum += x;}
};

如果ref库不提供operator(),那么它将无法用于标准库算法,因为标准库算法部使
用拷贝语义,算法内部的改变不能影响原对象,function可以提供一个略显麻烦但
可用的解法:
summary<int> s;
function<void(int const&)> func(ref(s)); // function包装引用
for_each(v.begin(), v.end(), func); //使用标准库算法

当需要存储一个可调用物用于回调的时候,最好使用function,它具有更多灵活性,
特别是把回调作为类的一个成员的时候我们只能使用function。

11.5 signals2
signals2基于Boost的另一个库signals,实现了线程安全的观察者模式。在signals2
库中,观察者模式被称为信号/插槽signals and slots,它是一种函数回调机制,一
个信号关联了多个插槽,当信号发出时,所有关联它的插槽都会被调用。

许多成熟的软件系统都用到了这种信号/插槽机制(另一个常用的名称是事件处理
机制:event/event handler),它可以很好地解耦一组互相协作的类,有的语言甚至
直接内建了对它的支持,如C#.

signal的模板参数列表相当长,总共有七个参数,而且除了第一个是必须的外,其
他的都可以使用默认值。

第一个模板参数Signature的含义与function的一模一样,也是一个函数类型签名,
表示可被signal调用的函数(插槽、事件处理handler)。例如:
signal<void(int, double)>

第二个模板参数combiner是一个函数对象,它被称为“合并器”,用来组合所有插槽
的调用结果,默认是optional_last_value<R>,它使用optional库返回最后一个
被调用的插槽的返回值。

第三个模板参数Group是插槽编组的类型,缺省使用int来标记组号,也可以改为
std::string等类型,但通常没有必要。

第四个模板参数GroupCompare与Group配合使用,用来确定编组的排序准则,默认是
升序std::less<Group>,因此要求Group必须定义了operator<。

signal继承自signal_base,而signal_base又继承自noncopyable,因此signal是
不可拷贝的,如果把signal作为自定义类的成员变量,那么自定义类也将是不可
拷贝的,除非使用shared_ptr来包装它。

11.5.2 操作函数
signal最重要的操作函数是插槽管理connect()函数,它把插槽连接到信号上,相当
于为信号(事件)增加了一个处理的handler.

插槽可以是任意的可调用对象,包括函数指针、函数对象、以及它们的bind表达式
和function对象,signal内部使用function作为容器来保存这些可调用对象。
连接时可以指定组号也可以不指定组号,当信息发生时将依据组号的排序准则依次
调用插槽函数。

如果连接成功,connect()将返回一个connection对象,表示了信号与插槽之间的连
这是一个轻量级的对象,可以处理两者间的连接,如断开、重连接、或者测试连接
状态。

成员函数disconnect()可以断开插槽与信号的连接,它有两种形式:传递组号将
断开该组的所有插槽,传递一个插槽对象将仅断开该插槽。函数
disconnect_all_slots()可以一次性断开信号的所有插槽连接。

当前信号所连接的插槽数量可以用num_slots()获得,成员函数empty()相当于
num_slots()==0,但它执行效率比num_slots()高。disconnect_all_slots()的
后果就是令empty()返回true;

signal提供operator(),可以接受最多9个参数,当operator()被外界调用时
意味着产生一个信号(事件),从而导致信号所关联的所有插槽被调用。插槽调用
的结果使用合并器处理后返回,默认情况下是一个optional对象。

成员函数combiner()和set_combiner()分别用于获取和设置合并器对象,通过
signal的构造函数也可以在创建的时候就传入一个合并器的实例。但通常我们可
以直接使用缺省构造函数创建模板参数列表中指定的合并器对象,除非像不想改用
其他的合并方式。

当signal析构时,将自动断开所有插槽连接,相当于调用disconnect_all_slots()。

11.5.3 插槽的连接与调用
signal就像是一个增强的function对象,它可以容纳(使用connect()连接)多个符
合模板参数中函数签名类型的函数(插槽),形成一个插槽链表,然后在信号发生时
一起调用。
除在类名字不同,signal的声明语法与function几乎一模一样。

然后我们就可以使用connect()来连接插槽,最后用operator()来产生信号。

void slots1()
{
cout<<"slot1 called"<<endl;
}

void slot2()
{
cout<<"slot2 called"<<endl;
}

int main()
{
signal<void()> sig; // 一个信号对象
sig.connect(&slots1); // 连接插槽1
sig.connect(&slots2); // 连接插槽2
sig(); // 调用operator(),产生信号(事件),触发插槽调用
}

在连接插槽时我们省略了connect()的第二个参数connect_position,它的缺省值是
at_back,表示插槽将插入到信号插槽链表的尾部,因此slots2将在slots1之后被
调用。我们还要可以使用at_front位置标志。
sig.connect(&slots2, at_front);
那么slots2将在slots1之前被调用。

使用组号:
connect()函数的另一个重载形式可以在连接时指定插槽所在的组号,缺省情况下组
号是int类型。组号不一定要从0开始连续编号,它可以是任意的数值、离散的、负值
都允许。

如果在连接的时候指定组号,那么每个编组的插槽将是又一个插槽链表,形成一个
略微有些复杂的二维链表。它们的顺序规则如下:
各编组的调用顺序由组号从小到大决定(也可以在signal的第四个模板参数改变
排序函数对象);
每个编组的插槽链表内部的插入顺序用at_back和at_front指定;
未被编组的插槽如果位置标志是at_front,将在所有编组之前调用;
未被编组的插槽如果位置标志是at_back,将在所有编组之后调用。

我们使用一个新的函数对象slots来演示一下signal的编组,它是一个模板类:
template <int N>
struct slots
{
void operator()()
{
cout<<"slot"<<N<<"called"<<endl;
}
};

signal的连接代码如下:
sig.connect(slots<1>(), at_back); // 最后被调用
sig.connect(slots<100>(), at_front); // 第一个被调用

sig.connect(5, slots<51>(), at_back); //组号5,该组最后一个
sig.connect(5, slots<44>(), at_front); // 组号5, 该组第一个

sig.connect(10, slots<10>()); // 组号10,该组仅有一个

11.5.4 信号的返回值
signal如function一样,不仅可以把输入参数转发给所有插槽,也可以传回插槽的
返回值。默认情况下signal使用合并器optional_last_value<R>,它将使用optional
对象返回最后被调用的插槽的返回值。

让signal支持拷贝
signal是noncopyable的子类,这意味着它不能被拷贝或者赋值。如果出于某种理由,
确实需要在多个对象之间共享signal对象,那么可以考虑使用
shared_ptr<signal<Signature> >作为类的成员,shared_ptr可以很好地管理
signal的共享语义。

插槽调度
因为signal会自动把解引用操作转换为插槽调用,所以自定义合并器某种程序上也相
当于一个插槽调度器。程序可以不需求所有的插槽被调用,只选择那些符合特定条件
的插槽,比如当一个插槽的返回值满足要求后就终止迭代,不再调用剩余的插槽。

线程安全
signal模板参数列表的最后一个类型参数是互斥量Mutex,默认值是signals2::mutex,
它会自动检测编译器的线程支持程度,根据操作系统自动决定要使用和的系统互斥
量对象(windows下使用临界区,UNIX下使用pthread_mutex),通常mutex都工作的很
好,不需要改变它。

signal对象在创建时会自动创建一个mutex保护内部状态,每一个插槽连接时也会创建
出一个新的mutex,当信号或插槽被调用时mutex都会自动锁定,因此signal可以很好
地工作于多线程环境。

同样地,connection和shared_connection_block也是线程安全的,但用于自动连接
管理的slot类不是线程安全的。

signals2库中还有一个dummy_mutex,它是一个空的mutex类,把它作为模板参数可以
使用signals2变成非线程安全的版本。

由于mutex是signal的最后一个模板参数,要指定它需要写出很多缺省的类型,signals2
使用元函数的方式可以便利地完成这个工作。

typedef signal_type<int(int), keywords::mutex_type<dummy_mutex> >::type 
signal_t;

关于function
signal内部使用function来存储可调用物,它的声明也与function很像,同样提供了
operator()。在signal只连接了一个插槽的时候基本上可以与function替换。

但需要注意它们的返回值,function对象直接返回被包装函数的返回值,而signal则
使用optional对象作为返回值,真正的返回值需要使用解引用操作符*才能取得。

与signals的区别
signals是Boost库中另一个信号/插槽库,实际上signals2的实现是基于signals的。
signals2与signals的最大区别是具有线程安全,能够用于多线程环境,而且不需要
编译就可以使用。

与C#的区别
signals2中的信号/插槽机制原理上类似于C#语言的event/delegate机制。
但C#的delegate的功能要比signals2弱,它要求精确的类型匹配,也没有合并器的概
念,只能返回一个结果。

delegate使得operator+=来连接event与delegate,signals2则使用connect()函数

11.6 总结
本章我们讨论了Boost库中五个用于函数和回调编程的组件。

result_of库,它很小但功能很强大,使用了模板元编程技术,可以帮助确定一个调用
表达式的返回类型,类似typeof库,主要用于泛型编程。

ref也是一个很小的库。它最初是tuple库的一部分,后来由于其重要性而被移出。
它能够包装对象的引用,变成一个可以拷贝、赋值的普通对象,因此减少了昂贵的
复制代价,标准库算法、tuple,bind,function等许多库都可以从ref库受益。

但Boost的ref库实现有个较大的缺陷,不支持operator()重载(函数调用)。

bind是一个功能强大的函数绑定器,它远远地超越了历史上出现过的各种函数绑定器,
包括标准库中的bind1st, bind2nd, mem_fun和非标准的select1st, select2nd,
compose_f_gx等等。它可以绑定任何可调用对象,搭配标准算法可以获得灵活操作
容器内元素的强大功能。bind还支持嵌套和操作符重载。

function库是函数指针的泛化,可以存储任意可调用的对象,因此function库经常
配合bind使用,它可以存储bind表达式的结果,以备之后调用。function具有很多优
点,它是泛型的,比普通的函数指针能够接受更多的可调用对象。function也可以
配合ref库使用,存储有内部状态的函数,

最后介绍的是signals2库,它综合运用了前四个组件,使用了信号/插槽机制,
是观察者设计模式的一个具体应用,也是一个功能强大的回调框架。使用signals2库
可以简化对象间的通信关系,降低它们的耦合性,只需要在程序开始时把它们连接
起来,之后的一切都会自动处理。

signals2还有许多的高级用法,可以使用合并器任意处理插槽的返回值,可以自动
跟踪插槽的生命周期。它也是线程安全的,能够被安全地应用在多线程程序中,而且
不需要预告编译。

第12章 并发编程
Boost在头文件<boost/detail/atomic_count.hpp>也提供了一个原子计数器---atomic
_count,它使用long进行线程安全的递增递减计数。

12.1.5 线程对象
thread类是thread库的核心类,负责启动和管理线程对象。

在使用thread对象时需要注意它是不可拷贝的,虽然它没有从boost::noncopyable继承
但thread内部把拷贝构造函数和赋值操作都声明为私有的,不能对它进行赋值或者
拷贝构造。

thread通过特别的机制支持转移语义,因此我们可以编写创建线程的工厂函数,封装
thread的创建细节,返回一个thread对象。

12.1.6 创建线程
线程的创建需要传递给thread对象一个可调用物(函数或函数对象),它必须具
有operator()以供线程执行。

如果可调用物不是无参的,那么thread的构造函数也支持直接传递所需的参数,这些
参数将被拷贝并在发生调用时传递给函数,thread的构造函数支持最多传递九个参数。

在传递参数是需要注意,thread使用的是参数的拷贝,因此要求可调用物和参数类型
都支持拷贝。如果希望传递给线程引用值就需要使用ref库进行包装,同时必须保证
被引用的对象在线程执行期间一直存在,否则会引发未定义的行为。

启动线程
当成功创建了一个thread对象后,线程就立刻开始执行,thread不提供类似start(),
begin()那样的方法、

如下函数,它向标准输出流打印字符串:
mutex io_mu; // io流是个共享资源,不是线程安全的,需要锁定
void printing(atom_int& x, const string& str) 
{
for(int i = 0; i < 5; ++i)
{
mutex::scoped_lock lock(io_mu); // 锁定io流操作
cout<<str<<++x<<endl;
}
}

那么我们可以这样使用thread对象:
int main()
{
atom_int x; // 原子操作的计数器
// 使用临时thread对象启动线程
thread(pirnting, ref(x), "hello"); // 向函数传递多个参数
thread(printing, ref(x), "boost"); // 使用ref库传递引用

this_thread::sleep(posix_time::seconds(2)); // 等待2秒钟
}
在当线程启动后我们必须调用sleep()来等待线程执行结束,否则会因为main()的
return语句导致主线程结束,而其他的线程还没有机会运行而一并结束。

join和timed_join
thread的成员函数joinable()可以判断thread对象是否标识了一个可执行的线程体。
如果joinable()返回true,我们就可以调用成员函数join()或者timed_join()来
阻塞等待线程执行结束。两者的区别如下:
join()一直阻塞等待,直到线程结束。
timed_join()阻塞等待线程结束,或者阻塞等待一定的时间段,然后不管线程是否
结束都返回。注意,这不必阻塞等待指定的时间长度,如果在这段时间里线程运行
结束,即使时间未到它也会返回。

使用join()可以这样操作thread对象:
atom_int x;
thread t1(printing, ref(x), "hello");
thread t2(printing, ref(x), "boost"); 
t1.timed_join(posix_time::seconds(1)); // 最多等待1秒然后返回
t2.join(); // 等待t2线程结束再返回,不管执行多少时间。

与线程执行体分离
可以使用成员函数detach()将thread与线程执行体手动分离,此后thread对象不代表任
何线程体,失去对线程体的控制。
thread t1(printing, ref(x), "hello"); // 启动线程
t1.detach(); // 与线程执行体分离,但线程继续执行
当thread与线程执行体分离时,线程执行体将不受影响地继续执行,直到运行结束,或
者随主线程一起结束。
当线程执行完毕或者thread对象被销毁时,thread对象也会自动与线程执行体分离,因
此,当不需要操作线程体时,我们可以使用临时对象启动一个线程。

使用bind和function
有时在thread的构造函数中写传递给调用函数的参数很麻烦,尤其是在使用大量线程
对象的时候。这时候我们可以使用Boost的bind和function库:bind库可以把函数所
需的参数绑定到一个函数对象,而function则可以存储bind表达式的结果,供程序以后
使用:
例如:
thread t3(bind(printing, ref(x), "thread")); // bind表达式
function<void()> f = bind(printing,5, "mutex"); // 使用function对象
thread(f);

12.1.7 操作线程
通常情况下一个非空的thread对象唯一地标识了一个可执行的线程体,是joinable()的
成员函数get_id()可以返回线程id对象。

线程id提供了完整的比较操作符和流输出操作,因此可以被放入标准容器用来管理线程
thread类也通过线程id支持线程间的比较操作。

thread类还提供了三个很有用的静态成员函数:yield()、sleep()和hardware
_concurrency(),它们用来在线程中完成一些特殊的工作。
yield()函数指示当前线程放弃时间片,允许其他的线程运行。
sleep()让线程睡眠等待一小段时间,注意它要求参数是一个system_time UTC时间点
而不是时间长度
haredware_concurrency()可以获得硬件系统可并行的线程数量,即CUP数量或者CPU内
核数量,如果无法获取信息则返回0。

例如,下面代码令当前线程睡眠1秒钟,然后输出可并行的线程数量:
thread::sleep(get_system_time()+posix_time::seconds(1));
cout<<thread::haredware_concurrency()<<endl;

thread库也在子名字空间this_thread里提供了3个自由函数---get_id()、yield()和
sleep()用于操作当前线程。它们的功能同thread类的同名函数,分别用来获得线程
id、放弃时间片和睡眠等待,但this_thread的sleep()函数不仅可以使用绝对的UTC
时间点,也可以使用时间长度。
例如:
this_thread::sleep(posix_time::seconds(2)); // 睡眠2秒钟
cout<<this_thread::get_id();
this_thread::yield();

12.1.8 中断线程
thread的成员函数interrupt()允许正在执行的线程被中断,被中断的线程会抛出一个
thread_interrupted异常,它是一个空类,不是std::exception或者boost::exception
的子类。thread_interrupted异常应该在线程执行函数里捕获并处理,如果线程不处理
这个异常,那么默认的动作是中止线程。

下面函数使用this_thread::sleep()睡眠1秒钟再输出字符串:
void to_interrupt(atom_int &x, const string &str)
try
{
for(int i = 0; i < 5; ++i)
{
this_thread::sleep(posix_time::seconds(1)); // 睡眠1秒钟
mutex::scoped_lock lock(io_mu); // 锁定io流操作
cout<<str<<++x<<endl; 
}
}
catch(thread_interrupted &) // 捕获中断异常
{
cout<<"thread_interrupted"<<endl; // 显示消息
}

main()启动这个线程,等待两钞钟,然后中断线程的执行:
int main()
{
atom_int x;
thread t(to_interrupt, ref(x), "hello");
this_thread::sleep(posix_time::seconds(2)); // 睡眠2秒钟
t.interrupt(); // 要求线程中断执行
t.join(); // 因为线程已经中断,所以join()立刻返回
}

线程的中断点
线程不是在任意时刻都可以被中断的。如果我们将to_interrupt()函数中的sleep()
睡眠等待去掉,那么即使在主线程中调用interrupt()线程也不会被中断。

thread库预定义了若干个线程的中断点,只有当线程执行到中断点的时候才能被中断,
一个线程可以拥有任意多个中断点。

thread库预定义了共9个中断点,它们都是函数,如下:
thread::join()
thread::timed_join();
condition_variable::wait()
condition_variable::timed_wait()
condition_variable_any::wait()
condition_variable_any::timed_wait()
thread::sleep()
this_thread::sleep()
this_thread::interuption_point()

这些中断点的前8个都是某种形式的等待函数,表明线程在阻塞等待的时候可以被中断。
而最后一个位于子名字空间this_thread的interruption_point()则是一个特殊的中断
点函数,它并不等待,只是起到一个标签的作用,表示线程执行到这个函数所在的
语句就可以被中断。

启用/禁用线程中断
缺省情况下线程都是允许中断的,但thread库允许控制线程的中断行为。
thread库在子名字空间this_thread提供了一组函数和类来共同完成线程的中断启用和
禁用:
interruption_enabled()函数检测当前线程是否允许中断。
interruption_requested()函数检测当前线程是否被要求中断。
类disable_interrruption是一个RAII类型对象,它在构造时关闭线程的中断,析构时
自动恢复线程的中断状态。

在disable_interruption的生命期内线程始终是不可中断的,除非使用了restore_
interruption对象。

restore_interruption只能在disable_interruption的作用域内使用,它在构造时
临时打开线程的中断状态,在析构时又关闭中断状态。

void to_interrupt(atom_int &x, const string& str)
try
{
using namespace this_thread; // 打开this_thread名字空间
assert(interruption_enabled()); // 此时允许中断
for(int i = 0; i < 5; ++i)
{
disable_interruption di; // 关闭中断
assert(!interruption_enabled()); // 此时中断不可用
mutex::scoped_lock lock(io_mu); // 锁定io流操作
cout<<str<<++x<<endl;
cout<<this_thread::interruption_requested()<<endl;
this_thread::interruption_point(); // 中断点被禁用

restore_interruption ri(di); // 临时恢复中断
assert(interruption_enabled()); // 此时中断可用
cout<<"can interrupted"<<endl;
cout<<this_thread::interruption_requested()<<endl;
this_thread::interruption_point(); // 可中断
} // 离开作用域,di/ri都被析构
assert(interruption_enabled()); // 此时允许中断
}
catch(thread_interrupted &)
{...}

12.1.9 线程组
thread库提供类thread_group用于管理一组线程,就像是一个线程池,它内部使用
std::list<thread*> 来容纳创建的thread对象。

thread_group的接口很小,用法也很简单.
成员函数create_thread()是一个工厂函数,可以创建thread对象并运行线程,同时加
入内部的list。我们也可以在thread_group外部创建线程对象,使用add_thread()
加入到线程组。

如果不需要某个线程,remove_thread()可以删除list里的thread对象。
join_all()和interrupt_all()用来对list里的所有线程对象操作,等待或中断这些
线程。
例如:
thread_group tg;
tg.create_thread(bind(printing, ref(x), "c++"));
tg.create_thread(bind(printing, ref(x), "boost"));
tg.join_all();
使用thread_group,我们可以为程序建立一个类似全局线程池的对象,统一管理程序中
使用的thread。这个可以使用前面的单件库变成一个单件,以提供一个全局的访问点,
例如:
typedef singleton_default<thread_group> thread_pool;
thread_pool::instance().create_thread(...);

不过Boost的单件库不提供完全的线程安全。

12.1.10 条件变量
条件变更是thread库提供的另一种用于等待的同步机制,可以实现线程间的通信,它
必须与互斥量配合使用,等待另一个线程中某个事件的发生(满足某个条件),然后
线程才能继续执行。

thread库提供两种条件变量对象condition_variable和condition_varriable_any,
一般情况下我们应该使用condition_vairable_any,它能够适应更广泛的互斥量
类型。

条件变量的使用方法很简单:
拥有条件变量的线程先锁定互斥量,然后循环检查某个条件,如果条件不满足,那么
就调用变量的成员函数wait()等待直至条件满足。其他线程处理条件变更要求的条件,
当条件满足时调用它的成员函数notify_one()或notify_all(),以通知一个或者所
有正在等待条件变更的线程停止等待继续执行。

其他用法
条件变量的wait()函数有一个有用的重载形式:wiat(lock_type& lock, predicate_type
predicate),它比普通的形式多接受一个谓词函数(或函数对象),当谓词predicate不
满足时持续等待。

使用这个重载形式或以写出更简洁清晰的代码,通常需要配合bind来简化谓词函数的
编写。
例如,buffer类的两个条件变量的等待可以改写成:
cond_put.wait(mu, !bind(&buffer::is_full, this));
cond_get.wait(mu, !bind(&buffer::is_empty, this));

12.1.11 共享互斥量
共享互斥量shared_mutex不同于mutex和recursive_mutex,它允许线程获取多个共享
所有权和一个专享所有权,实现了读写锁的机制,即多个读线程一个写线程。

shared_mutex具有mutex的全部功能,可以把它像mutex一样使用lock()和unlock()
来获得专享所有权,但它代价要比mutex高很多。如果要获得共享所有权需要使用
lock_shared()或try_lock_shared(),相应地要使用unlock_shared()来释放共享
所有权。

shared_mutex没有提供内部的lock_guard类型定义,因此在使用shared_mutex时我们
必须直接使用lock_guard对象,读锁定时使用shared_lock<shared_mutex>,写锁定
时使用unique_lock<shared_mutex>。

以下使用shared_mutex实现多个读者一个作者:
class rw_data
{
private:
int m_x; // 用于读写的数据
shared_mutex rw_mu; // 共享互斥量
public:
rw_data():m_x(0){} // 构造函数
void write()// 写数据
{
unique_lock<shared_mutex> ul(rw_mu); // 写锁定
++m_x; 
}

void read(int *x) // 读数据
{
shared_lock<shared_mutex> sl(rw_mu); // 读锁定
*x = m_x;
}
};

12.1.12 future
很多情况下线程不仅仅要执行一些工作,它还可能要返回一些计算结果。

thread库使用future范式提供了一种异步操作线程返回值的方法,

future使用packaged_task和promise两个模板类来包装异步调用,用unique_future
和shared_future来获取异步调用结果(即future值)。

package_task和unique_future
packaged_task用来存储packaged_task异步计算得到的future值,它只能持有结果的
唯一的一个引用。成员函数wait()和timed_wait()的行为类似thread.join(),可以
阻塞等待packaged_task的执行,直至获得future值。成员函数is_ready()、has_value()
和has_exception()分别用来测试unique_future是否可用。

下面代码示范了future特性的用法,使用packaged_task和unique_future:
int fab(int n)
{
if (n == 0 || n ==1)
{return 1;}
return fab(n-1) + fab(n-2);
}

int main()
{
// 声明packaged_task对象,用模板参数指明返回值类型
// packaged_task只接受无参函数,因此需要使用bind
packaged_task<int> pt(bind(fab, 10));

// 声明unique_future对象,接受packaged_task的future值
// 同样要用模板参数指明返回值类型
unique_future<int> uf = pt.get_future();

// 启动线程计算,必须使用boost::move()来转移package_task对象
// 因为packaged_task是不可拷贝的
thread(boost::move(pt));
uf.wait(); // unique_future等待计算结果
assert(uf.is_ready() && uf.has_value()); 
cout<<uf.get(); // 输出计算结果99
}

使用多个future对象
为了支持多个future对象的使用,future还提供wait_for_any()和wiat_for_all()两个
自由函数,它们可以阻塞等待多个future对象,直到任意一个或者所有future对象都
可以(is_ready())。这两个函数有多个重载形式,可以接受一对表示future容器区间
的迭代器或者最多5个future对象。

promise
promise也用于处理异步调用返回值,但它不同于packaged_task,不能包装一个函数,
而是包装一个值,这个值可以作为函数的输出参数,适用于从函数参数返回值的函数。

promise的用法与packaged_task类似,在线程中用set_value()设置要返回的值,用
成员函数get_future()获得future值赋给future对象。

12.2 asio
asio库基于操作系统提供的异步机制,采用前摄器设计模式proactor实现了可移植的
异步(或者同步)IO操作,而且并不要求使用多线程和锁定,有效地避免了多线程
带来的诸多有害副作用(如条件竞争、死锁等)。

目前asio主要关注于网络通信方面,使用大量的类和函数封装了socket API,提供了
一个现代C++风格的网络编程接口,支持TCP、ICMP、UDP等网络通信协议。但asio的
异步操作并不局限于网络编程,它还支持串口读写、定时器、SSL等功能。而且
asio是一个很好的富有弹性的框架,可以扩展到其他有异步操作需要的领域。

使用asio不需要编译,但它依赖于其他一些Boost库组件,最基本的是boost.system
和boost.datetime库,用来提供系统错误和时间支持。其他的可选库有regex、thread
和serialization,如果支持SSL,还要额外安装OpenSSL.

12.2 asio
asio库基于操作系统提供的异步机制,采用前摄器设计模式proactor实现了可移植的
异步(或者同步)IO操作,而且并不要求使用多线程和锁定,有效地避免了多线程编程
带来的诸多有害副作用(如条件竞争、列锁等)。

目前asio主要关注于网络通信方面,使用大量的类和函数封装了socket API,提供了一
个现代C++风格的网络编程接口,支持TCP、ICMP、UDP等网络通信协议。但asio的异步
操作并不局限于网络编程,它还支持串口读写、定时器、SSL等功能,而且asio是
一个很好的富弹性的框架,可以扩展到其他有异步操作需要的领域。

本章下面对asio的介绍将以基本功能为主,即不使用system和datetime以外的Boost
组件。

12.2.1 概述
asio库基于前摄器模式proactor封装了操作系统的select、poll/epoll、kqueue、
overlapped I/O等机制,实现了异步IO模型。它的核心类是io_service,相当于前
摄器模式中的proactor角色,asio的任何操作都需要有io_service的参与。

在同步模式下,程序发起一个IO操作,向io_service提交请求,io_service把操作
转交给操作系统,同步地等待。当IO操作完成时,操作系统通知io_service,然后
io_service再把结果发回给程序,完成整个同步流程。这个处理流程与多线程的
join()等待方式很相似。

在异步模式下,程序除了要发起的IO操作,还要定义一个用于回调的完成处理函数。
io_service同样把IO操作转交给程序系统执行,但它不同步等待,而是立即返回。
调用io_service的run()成员函数可以等待异步操作完成,当异步操作完成时io_service
从操作系统获取执行结果,调用完成处理函数。

asio不直接使用操作系统提供的线程,而是定义了一个自己的线程概念:strand,它保证
在多线程的环境中代码可以正确的执行,而无需使用互斥量。io_service::strand::
wrap()函数可以包装一个函数在strand中执行。

IO操作会经常使用到缓冲区,asio库专门用两个类mutable_buffer和const_buffer
来封装这个概念,它们可以被安全地应用在异步的读写操作中,使用自由函数buffer()
能够包装常用的C++容器类型,如数组、array、vector、string等,用read()、write()
函数来读取缓冲区。

asio库使用system库的error_code和system_error来表示程序运行的错误。基本上所
有的函数都有两种重载形式,一种形式是有一个error_code的输出参数,调用后必
须检查这个参数验证是否发生了错误;另一种形式没有error_code参数,如果发生了
错误会抛出system_error异常,调用代码必须使用try_catch块来捕获错误。

12.2.2 定时器
定时器是asio库里最简单的一个IO模型示范,提供等候时间终止的功能。
定时器deadline_timer有两种形式的构造函数,都要求有一个io_service对象,用于
提交IO请求,第二个参数是定时器的终止时间。可以是posix_time的绝对时间点或者
是自当前时间开始的一个时间长度。

一旦定时器对象创建,它就会立即开始计时,可以使用成员函数wait()来同步等待
定时器终止,或者使用async_wait()异步等待,当定时器终止时会调用handler函数。

如果创建定时器时不指定终止时间,那么定时器不会工作,可以使用成员函数
expires_at()和expires_from_now()分别设置定时器终止的绝对时间和相对时间,然后
再调用wait()和async_wait()等待。这两个函数也可以用于获得定时器的终止时间,只
需要使用它们的无参重载形式。

定时器还有一个cancel()函数,它的功能是通知所有异步操作取消,转而等待定时器
终止。

同步定时器
下面示范了deadline_timer的用法
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::asio; // 打开asio名字空间
int main()
{
io_service ios; // 所有的asio程序必须要有一个io_service对象
deadline_time t(ios, posix_time::seconds(2)); // 定时器,io_serice作为构造函数
的参数, 两秒钟后定时器终止
cout<<t.expires_at()<<endl; // 查看终止的绝对时间 
t.wait(); // 调用wait()同步等待
cout<<"Hello asio"<<endl; // 输出一条消息
}

读者可以把它与thread库的sleep()函数对象研究一下,两者虽然都是等待,但内部
机制完全不同:thread库的sleep()使用了互斥量和条件变量,在线程中等待,而asio
则是调用了操作系统的异步机制,如select、epoll等完成的。

上面演示了asio程序的基本结构和流程:一个asio程序首先要定义一个io_service对象
它是前摄器模式中最重要的proactor角色,然后我们声明一个IO操作(在这里是定时
器),并把它挂接在io_service上,再然后就可以执行后续的同步或异步操作。

异步定时器
代码与同步定时器大致相同,增加了回调函数,并使用io_service.run()和定时器的
async_wait()方法。

首先我们需要定义一个回调函数,asio库要求回调函数只能有一个参数,而且这个
参数必须是const asio::error_code& 类型:
void print(system::error_code &/*e*/)
{
cout<<"hello asio"<<endl;
}

随后的异步定时器代码也同样很简单:
int main()
{
io_service ios;
deadline_timer t(ios, posix_time::seconds(2)); // 定时器
t.async_wait(print); // 异步等待,传入回调函数
cout<<"it show before t expired."<<endl;
ios.run(); // 很重要,异步IO必须
}
当定时器时间终止时io_service将调用被注册的print(),输出一条消息,然后程序
结束。

异步定时器使用bind
由于async_wait()接受的回调函数类型是固定的,必须使用bind库来绑定参数以适
配它的接口。

下面是可以定时执行任意函数的定时器。它持有一个asio定时器对象和一个计数器
还有一个function()对象用来保存回调函数。

12.2.4 asio库支持TCP,UDP和TCMP通信协议,它在名字空间boost::asio::ip里
提供了大量的网络通信方面的函数和类,很好地封装了原始的Berkeley Socket API,
展现给asio用户一个方便易用且健壮的网络通信库。

12.2.8 查询网络地址
之前关于TCP通信的所有论述我们都是使用直接的IP地址,但在实际中大多数只有一个
域名,这时候我们就需要使用resolver类来通过域名获得可用的IP,它可以实现与
IP版本无关的网址解析。

串口通信
asio除网络通信处,也支持串口通信,需要使用serial_port类。串口通信的基本处理
流程与网络通信类似,但要在通信前设置好波特率、奇偶校验位等串口通信参数。
int main()
{
io_service ios;
serial_port sp(ios, "COM1"); // 打开串口COM1

// 设置串口参数
sp.set_option(serial_port::baud_rate(9600));
sp.set_option(serial_port::flow_control(serial_port::flow_control::none));
sp.set_option(serial_port::parity(serial_port::parity::none));
sp.set_option(serial_port::stop_bits(serial_port::stop_bits::one));
sp.set_option(serial_port::character_size(8)); 
size_t len = sp.write_some(buffer("hello serial")); // 向串口写数据
cout<<len<<endl;

vector<char> v(100);
sp.async_read_some(buffer(v), bind(read_handler, placeholders::error));
// 异步接收数据
deadline_timer t(ios, posix_time::seconds(2)); // 处理超时
t.async_wait(bind(time_exipred, placeholders::error, &sp)); 
ios.run(); // 启动事件等待循环
}

第13章 编程语言支持
boost中的python库可以更加方便和容易地在python和C++之间自由转换,而且功能更强
大。python库全面支持C++和python的各种特性,包括C++到python的异常转换、默认
参数、关键字参数、引用和指针等等,让C++与python可以近乎完美地对接:用C++很
容易地为python编写扩展模块,也可以很容易地在C++代码中执行python程序。

python是一种简洁的、功能强大的、动态强类型的解释型语言。
python具有比C/C++语言还要简洁的语法,使用代码缩进而不是分号来分隔语句,同时
简化了许多传统的语法结构,从而具有优雅的代码格式。

python也有具有强大的功能,内建了多种高级数据结构,如列表、集合、元组和字典,
python完全支持面向过程编程和面向对象编程(实际上,python里的一切几乎都是对象)
支持异常、名字空间、操作符重载等现代编程机制。它还有一个强大的标准库和许多
第三方库,如正则表达式、数据库、XML、电子邮件、测试、图形界面。。。如果想
要定制功能也很容易,python可用C语言扩展底层,增添新模块。

python是一个动态语言,类似php、perl,无需声明变量类型就可以使用。但它又是
强类型的,变量一旦初始化,就不能随意改变类型。python不需要编译,它可以动态
地解释交互运行,类似BASIC,这大大缩短了程序的开发周期。但python也能够像java
一样编译成字节码后运行在虚拟机(解释器)上以获得更高的效率。

python是可移植的,在各种操作系统上都有免费的python解释器。在这些平台上python
可以代替批处理、shell、或者perl,编写脚本程序方便日常工作,也可以开发非常
复杂的应用程序或者服务程序,python能够做其他语言所能做的所有事情。

最初的python是用C语言写成的,后又逐渐出现了其他语言编写的python,如java的
Jython和C#的IronPython,它们不仅具有python的功能,还可以调用宿主语言,更为
强大。因此,传统的python有时又被称为C-Python。

13.1.3 编译python库
boost.python库需编译才能使用,要求前提是已经安装了python环境。

13.1.4 使用python库
python库位于名字空间boost::python,为了使用python库,需要包含头文件<boost/
python.hpp>。

13.2 嵌入python
我们先从python库最简单的用法---嵌入python语句开始。这种使用方式可以调用python
语言的标准库和第三方库,就像拥有了一群数量庞大的库函数,让C++不费任何力气就
拥有了脚本语言的操纵能力。

嵌入python语言需要链接python的运行库python26.lib。它在c:/Python26/Libs目录下,
可以在VC的工程属性中设置链接库选项,但最好是使用预处理指令放在源代码中,如:
#pragma comment(lib, "python26.lib") // vc系列编译器支持这个指令
#define BOOST_PYTHON_SOURCE
#include <boost/python.hpp>
using namespace boost::python;

13.2.1 初始化解释器
在C程序中执行python语句有一个标准流程:
首先要调用python API函数Py_Initialize()启动Python解释器;
解释器启动后,可以使用Py_IsInitialized()来检查解释器是否已经成功启动;
在完成所有的Python调用后,使用Py_Finalize()来清除解释器环境。

目前的boost.python库不完全遵循上面的流程,库文档建议不执行Py_Finalize()来清除
环境,因此在C++中只需要调用Py_Initialize()就可以了。

Python API相当的简陋,而python库并没有对它进行封装,读者可以自已对python库
的API进行封装,使它使用更方便。

13.2.2 封装python对象
python库使用模板类handle和object封装了Python API中的PyObject类型,handle是一
个智能指针,一般情况下我们应当优先使用object。

object类封装了PyOjbect,内部也使用了引用计数,使用起来就像Python语言中的
原生变量,或者是C++中的auto和boost.any。

13.2.3 执行Python语句
启动python解释器后,可以使用python库提供的exe()系列函数执行python语句,这些
函数的声明如下:
object eval(str expression, object globals, object locals)
object exec(str code, object globals, object locals)
object exec_file(str filename, object globals, object locals)
这三个函数的功能类似,都可以执行python语句,但有小的不同:eval()函数计算表达
式的值并返回结果,exec()执行python语句并返回结果,而exec_file()则执行一个文件
中的python代码。

函数接口中的globals和locals参数是python中的字典结构,是语句运行的全局和局部
场景,通常这两个参数可以忽略,或者取__main__模块的名字空间字典。

例如:
cout<<extract<int>(eval("3**3"))<<endl; // 计算3的立方
exec("print "hello python""); // 输出语句

如果在python语句使用了变量,那么必须要指定globals参数,使用import()函数可以
导入__main__模块,用成员函数attr()获取属性。

#pragma comment(lib, "python26.lib")
#define BOOST_PYTHON_SOURCE
#include <boost/python.hpp>
using namespace boost::python;

int main()
{
Py_Initialize();

// 获取运行所需的名字空间
object main_ns = import("__main__").attr("__dit__");

// 执行for 循环
string str = "for x int range(1, 5):\n"
"\tprint x";
exec(str.c_str(), main_ns); // 输出1,2,3,4

// 定义一个python函数,计算x的y次方
char *funcdef = "def power(x, y):\n"
"\t return x**y\n"
"print power(5, 3)\n"; 
exec(funcdef, main_ns); // 输出125
object f = main_ns["power"]; // 使用名字空间字典获得函数对象
cout<<extract<int>(f(4,2))<<endl; // 用operator()执行

// 导入re模块,执行正则表达式功能,输入true
exec("import re", main_ns);
exec("print re.match('c*', 'ccc') != None", main_ns);
}

异常处理
如果执行python语句发生错误,python库会抛出error_already_set异常,但不含有
任何信息,需要调用API函数PyErr_Print()向标准输出打印具体的错误信息。

13.3 扩展Python
python库能够在C++程序中调用Python语言,但它更重要的功能在于用C++编写Python
扩展模块,嵌入到Python中解释器中调用,提高Python的执行效率。

扩展Python我们同样需要编译python库,并包含头文件<boost/python.hpp>,但不必
指明Python的运行库即可自动链接(需设定库文件搜索路径,默认是C:\Python26\libs)

首先我们要在VC中建立一个DLL工程,名字叫boostpy,是一个不使用预编译头的空工程,
不要忘记设置工程字符集的Not Set和运行多线程的MT或MTD属性,并加入宏"_STLP_DEBUG"
和"__STL_DEBUG"(如果使用了STLport)。

接下来我们在boostpy.cpp中编写函数的导出代码,使用BOOST_PYTHON_MODULE宏定义
Python模块名,模块名必须与dll的名字相同,当然也可以在编译后改dll的名字。

// 需要导出的函数
string hello_func()
{
return "hello python";
}

在宏BOOST_PYTHON_MODULE定义的模块内部我们使用def()函数定义要导出的函数,需要
指定导出的名字和C++的函数名字,它的语法一定程序上模仿了Python语言的函数定义
关键字def。

#define BOOST_PYTHON_SOURCE // 源码嵌入工程编译的方式
#include <boost/python.hpp> 
using namespace boost::python; // 打开名字空间
string hello_func(){...} // C++函数定义

BOOST_PYTHON_MODULE(boostpy)
{
// 导出一个只字为hello的函数,其doc string是“函数说明字符串”
def("hello", hello_func, "函数说明字符串");
}

不需要担心这个简短的程序中没有DllMain(),WINAPI等Windows编程中常见的dll导出
元素,也不需要写def或者exp文件,python库为我们在幕后做了一切,将自动生成一个
完全可用的动态链接库boostpy.dll。

但这个dll不能直接被python环境识别,必须把后缀名改成标准的python模块后缀名pyd
即boostpy.pyd(也可以修改VC工程设置,直接生成后缀是pyd的dll文件),然后放置
到python环境可以找到的路径下---通常是python主目录或主目录下的lib目录.
这样,我们就可以在python交互解释器IDLE测试这个最小的python扩展模块。

boostpy模块也可以在python脚本中运行,使用脚本运行扩展模块时不要求pyd模块必
须在python主目录下,只要和脚本在同一个目录即可。
例如下面的test.py脚本:
#coding:uft-8
#file test.py
import boostpy  #导入boostpy模块
print boostpy.hello() #使用print语句输出结果

13.3.2 导出函数
python库使用模板函数def()来导出C++函数到python,它有多个重载形式,声明是:
template <class F>
void def(char const* name, F f,...);
def()函数要求导出的名字必须是C字符串(以0结束的字符数组),不能是std::string
对象,第二个参数是类型为F的函数指针,这之后可以添加文档字符串以及函数的参数
列表等函数的附加信息。

向python导出函数的参数需要使用python库中的参数关键字类arg:

为了支持C++和Python的缺省参数特性,arg重载了operator=,可以指定参数的缺省值。
它还重载了逗号操作符,可以使用逗号把arg参数连接起来(很像assign库的用法)。但
在def()函数中使用时,为了不与函数参数分隔的逗号混淆,我们需要把arg逗号表达
式用圆括号括起来。

导出带有参数的C++函数:
string hello_to(const string& str) // 接收一个字符串参数
{return "hello" + str;}

string hello_x(const string& str, int) // 接收字符串和整数参数
{
string tmp += "hello";
for (int i = 0; i < x; ++i)
{
tmp += "str" +" ";
}
return tmp;
}

然后我们在宏BOOST_PYTHON_MODULE定义的导出模块中导出它们,并使用arg类定义它
们在python中的参数。
def("helloto", hello_to, arg("str")); // 定义一个参数
def("helloto", hello_x, (arg("str"), "x")); // 使用逗号操作符

在导出函数时参数名不一定非要与C++中的一致,可以是任意的名字,只要它符合
python的命名规则即可。

编译生成新的pyd文件后,可以使用下面的python脚本调用验证。

print boostpy.hello_to('boost')
print boostpy.hello_x('C++', 5);

python库另外提供了一个便捷的函数args(),可以用在不需要指定参数值的时候直接
使用参数名字符串生成多个arg对象,例如:
def("hellox", hello_x, args("str", "x"));

13.3.3 导出重载函数
C++和python中都有重载函数的概念,它们可以名字相同但参数和返回值不同。
python导出C++重载函数的时候不能使用之前的def()形式,因为无法从函数名字区分
出重载函数,必须使用函数指针的类型定义

使用宏自动导出
如果程序中有大量的重载函数,那么手工定义函数指针的工作将会很繁琐,因此
python库特意提供了一个方便的宏BOOST_PYTHON_FUNCTION_OVERLOADS,专门用于简化
重载函数的定义,它可以自动产生重载函数说明,声明如下:
#define BOOST_PYTHON_FUNCTION_OVERLOADS(generator_name, fname, min_args, 
max_args)

使用BOOST_PYTHON_FUNCTION_OVERLOADS有一点限制,要求重载函数必须具有顺序相同
的参数序列,即少的参数序列是多的参数序列的子集。
宏BOOST_PYTHON_FUNCTION_OVERLOADS 可以这样使用:
BOOST_PYTHON_FUNCTION_OVERLOADS(hello_overloads, hello_func, 0, 2)
其中hello_overloads是我们为重载函数指定的辅助类名,hello_func是重载函数的名
字,数字0和2表示有三个重载形式,参数最少是0个,最多是3个。

在使用def()导出前,我们还必须定义最多参数的重载函数指针类型,即:
typedef string (*hello_ft) (const string&, int);
然后我们就可以向Python一次性导出全部重载函数:
def("hello", (hello_ft)0, hello_overloads());
注意,上面def()函数中第二个参数,即函数指针参数的用法,我们把一个空指针
转换成了hello_ft函数指针类型,然后再用辅助类的临时对象hello_overloads()以
导出所有函数。

我们也可以仍然使用函数指针,但需要用最多参数的那个函数指针:
def("hello", fp3, hello_overloads());

BOOST_PYTHON_FUNCTION_OVERLOADS的另一个用法是导出具有缺省参数值的函数,这种
函数就像是有N个重载形式的函数,例如:
string hello_func(const string& str = "boost", int x = 5);
typedef string(*hello_ft)(const string&, int);
def("hello", (hello_ft)0, hello_overloads());
对于有相同参数序列或者缺省参数的函数使用宏BOOST_PYTHON_FUNCTION_OVERLOADS,
其他的则使用手工定义函数指针的方式。

13.3.4 导出类
python库的另一个强大的功能是可以方便地把C++类导出为Python类,这在python库
出现前是一件非常烦琐且容易出错的工作。python库使用一个类似python语法的模板
类“Class"封装了这项工作。

class_类的用法与def()函数基本相同,它导出模板参数T类型为python类,再用成员
函数def()、def_readonly()等分别导出T的成员函数和成员变量。

例如,我们把之前的hello()系列函数改为一个简单的C++类:
class demo_class
{
private:
string msg;
public:
static string s_hello;
demo_class():msg("boost"){} // 缺省构造函数
string hellox(int x = 1)
{
string tmp = s_hello;
for (int i = 0; i < x; ++i)
{
tmp += msg + "";
}
return tmp;
}
};
string demo_class::s_hello = "hello"; // 静态成员变量初始化

那么使用class_可以这样导出类:
BOOST_PYTHON_MODULE(boostpy)
{
class_<demo_class>("demo", "doc_string")
.def("hello", &demo_class::hellox, arg("x")=1)
.def_readwrite("shello", &demo_class::s_hello);
}

构造函数
我们不能使用def()来导出构造函数,因为C++中的构造函数不同于普通的成员函数,最
重要的区别是不能取它的地址,即没有这样的语法:
&demo_class::demo_class
因此,python库使用模板类init<...>和optional<...>来共同定义构造函数和构造函数
中的缺省参数。它们的模板参数都是构造函数的参数类型,init中的参数是必须出现的,
而optional中的参数是有缺省值可以不出现的,它们的用法很像定义重载构造函数。

导出property
使用def_readonly()和def_readwrite()我们可以导出C++类的成员函数,同时指定它
的读写属性,但这两个函数要求类的成员变量必须是public的。

第14章 其他Boost组件
14.1 字符串和文件处理
regex
spirit:
spirit是一个面向对象的递归下降解析器的生成框架,它使用EBNF语法,是一个比正则
表达式更强大的语法分析器。

14.2 容器与数据结构
gil
它是一个通用图像库,它为像素、色彩、通道等图像处理概念提供了泛型的、STL式
的容器和算法,可以对图像做灰度化、梯度、均值、旋转等许多运行,支持JPG、
PNG,TIFF等文件格式。

graph库处理离散数学中的图结构,并提供图、矩阵等数据结构上的泛型算法,例如
Dijkstra最短路径、Prim最小生成树、连通分支等,可以看作是STL在非线性容器
领域的扩展。

intrusive

pointer container提供了与STL类似的若干种指针容器,包括ptr_vector、ptr_list、
ptr_map等,性能较好且异常安全。

multi_index

iterator

range

14.4 函数对象与高级编程
mem_fn 是STL中函数对象适配器mem_fun和mem_fun_ref的扩展,类似bind与bind1st,
bind2nd的关系,可以完全取代C++98中的成员函数适配器,能够把成员函数适配为
可用于STL算法的函数对象。

mem_fn的用法与bind很类似,仅仅是少传递了一个类实例参数,在大多数情况下
bind可以完全替代mem_fn。

functional

hash

lambda

signals实现了观察都模式,功能和用法与signals2基本相同,但不支持线程安全,而
且需要编译,应该使用signals2库。

parameter

14.5 泛型编程
enable_if为允许模板函数或者模板类在偏特化时针对某些特定类型有效,即启用或
禁用某些特化形式,它依赖于SFINAE原则。

call_traits
class_traits<T>封装了可能是最好的传递参数给函数的方式,它会自动推导出最高
效的传递参数的类型:内建类型传值,类实例则传引用,而且保证不会出现”引用的
引用“这个非法的错误。

type_traits
type_traits提供了一组特征(trait)类,用以在编译期确定类型是否具有某些特征,
例如类型T是否是一个指针,是否是个抽象类,是否重载了new操作符等,它还包含一
套可以对某个类型执行特定转换的工具类。使用type_traits可以编写出更高效的泛型
代码。
type_traits已被收入TR1中。

concept check主要被用来编写实现泛型算法或泛型库。

function_types这个库提供了对函数、函数指针、函数引用和成员指针等类型进行分类、
分解和合成的功能。

in_place_factory库是工厂设计模式的一种实践,允许就地直接构造对象而不需要一
个临时对象的拷贝。

proto库允许在C++中构建专用领域嵌入式语言,

property_map是一个概念库,提供了key_value映射的属性概念定义,为从键到值的
映射定义了一个通用接口

14.6 模板元编程
fusion库提供基于tuple的编译期容器(vector, set, map等)和算法,是模板元编程
的强大工具,可以与mpl很好地协同工作。

map是一个模板元编程框架,包含有编译期的算法、容器和函数等完整的元编程工具。
运用mpl,很多运行时的工作都可以在编译期完成,甚至编译结束就意味着程序的
运行结束。

14.7 预处理元编程
preprocessor
wave

14.8 并发编程
interprocess实现在可移植的进程间通信IPC的功能,包括共享内存、内存映射文件、
信号量、文件锁、消息队列等许多现代操作系统的IPC机制,并提供了简洁易用的
STL风格接口,大大简化了IPC编程工作。

MPI库可用于高性能分布式并行计算应用的开发,它封装了标准的MPI(消息传送
接口)以更好地支持现代C++编程风格。

14.9 数学与数字
accumulators

interval库处理“区间”概念相关的数学问题。

map库包括common_factor,octonion,quaternion等六个组件,包含了大量数学领域
的模板类和算法,如复数的反三角函数asin, acos、最大公约数和最小公倍数(lcm,
gcd)、由元数、八元数,还有许多特殊数学函数和统计分布(拉格朗日多项式、
椭圆积分、X方分布,伯努得分布等)。

uBLAS是一个用于线性代数领域的数学库。它支持单位向量、稀疏向量、密集矩阵、
稀疏矩阵、三角矩阵等许多线性代数概念,可以对向量或矩阵进行加法和减法,与
标量做乘法,执行内积、外积等许多矩阵运行,这些接口被设计成与STL类似的风格。

14.10 TR1
TR1(Technical Report1)C++库扩展技术报告。

第15 章 Boost与设计模式
23个设计模式,分为创建型模式、结构型模式和行为模式三个大类。

《Boost程序完全开发指南》相关推荐

  1. ComeFuture英伽学院——2020年 全国大学生英语竞赛【C类初赛真题解析】(持续更新)

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  2. ComeFuture英伽学院——2019年 全国大学生英语竞赛【C类初赛真题解析】大小作文——详细解析

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  3. 信息学奥赛真题解析(玩具谜题)

    玩具谜题(2016年信息学奥赛提高组真题) 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业.有一天, 这些玩具小人把小南的眼镜藏了起来.小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的 ...

  4. 信息学奥赛之初赛 第1轮 讲解(01-08课)

    信息学奥赛之初赛讲解 01 计算机概述 系统基本结构 信息学奥赛之初赛讲解 01 计算机概述 系统基本结构_哔哩哔哩_bilibili 信息学奥赛之初赛讲解 02 软件系统 计算机语言 进制转换 信息 ...

  5. 信息学奥赛一本通习题答案(五)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  6. 信息学奥赛一本通习题答案(三)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  7. 信息学奥赛一本通 提高篇 第六部分 数学基础 相关的真题

    第1章   快速幂 1875:[13NOIP提高组]转圈游戏 信息学奥赛一本通(C++版)在线评测系统 第2 章  素数 第 3 章  约数 第 4 章  同余问题 第 5 章  矩阵乘法 第 6 章 ...

  8. 信息学奥赛一本通题目代码(非题库)

    为了完善自己学c++,很多人都去读相关文献,就比如<信息学奥赛一本通>,可又对题目无从下手,从今天开始,我将把书上的题目一 一的解析下来,可以做参考,如果有错,可以告诉我,将在下次解析里重 ...

  9. 信息学奥赛一本通(C++版) 刷题 记录

    总目录详见:https://blog.csdn.net/mrcrack/article/details/86501716 信息学奥赛一本通(C++版) 刷题 记录 http://ybt.ssoier. ...

  10. 最近公共祖先三种算法详解 + 模板题 建议新手收藏 例题: 信息学奥赛一本通 祖孙询问 距离

    首先什么是最近公共祖先?? 如图:红色节点的祖先为红色的1, 2, 3. 绿色节点的祖先为绿色的1, 2, 3, 4. 他们的最近公共祖先即他们最先相交的地方,如在上图中黄色的点就是他们的最近公共祖先 ...

最新文章

  1. C/C++中extern关键字详解
  2. 休眠:保存vs持久并保存或更新
  3. 计算机网络 --- 数据链路层aloha协议
  4. python3.9性能_Python 3.9 性能优化:更快的 list()、dict() 和 range() 等内置类型
  5. 数据科学家最常用的十种算法(我准备拿这个当成学习参考)
  6. 用stack实现括号匹配
  7. 记redis的一个测试
  8. 概论-组合最优化问题、计算复杂性和启发式算法概念(现代优化计算方法)
  9. 微服务架构设计思维导图总结
  10. 论文阅读笔记|Deep Image Homography Estimation
  11. c语言中闰年的流程图_c语言(算法流程图).ppt
  12. 关于html引用css无法加载——新手踩的坑
  13. 嵌入式UWB定位测距设备开发实战(4)硬件之元器件选型
  14. 八字算命网站源码技术细节解析:使用PHP和JAVA实现的MVC架构、MySQL数据库设计和功能模块介绍
  15. 广西国家级自然保护区功能区划图(展示)
  16. C# DGV常用操作
  17. linux安装mysql出现Could NOT find Curses (missing CURSES_LIBRARY CURSES_INCLUDE_PATH),提示解决方法
  18. 201771010137赵栋《第八周学习总结》
  19. 混合云的那些事:如何做到让公有云和私有云实现1+12
  20. 雷诺手表怎么看型号?rarone型号查询的方法

热门文章

  1. https利用360cdn实现网站加速
  2. 利用Python(pyserial、minimalmodbus、modbus_tk)进行单片机通信
  3. mysql有numeric类型吗_mysql数值类型 - numeric
  4. 批量删除,未勾选数据,点击【批量删除】弹出确认删除提示框
  5. 鸡头稳如云台_稳如“鸡头”?魔爪Mini-MI智能手机三轴稳定器 开箱
  6. wxpy将个人微信号变成微信聊天机器人
  7. KVM虚拟化,云平台
  8. http接口测试:了解协议、请求方法、响应状态码
  9. ae渲染存在偏移_基于三维GIS技术的矢量地图动态LOD渲染方法
  10. 论文笔记27 -- (视频压缩)Learned Video Codec with Enriched Reconstruction for CLIC P-frame Coding