代码改变世界

[Boost系列] Boost学习

2011-04-20 09:10  zhenjing  阅读(3624)  评论(0编辑  收藏  举报

Boost 学习



 作者:陈珍敬

Boost 安装

下载 boost 源代码包,解压,进入 boost 目录,执行 ./configure-> 生成 Makefile-> 执行 make 即可。

自动执行:

./tools/build/jam_src/bin.linuxx86/bjam -sPYTHON_ROOT=/usr -sPYTHON_VERSION=2.3 -sTOOLS=gcc

 

法二:

configue 之后,将 bjam 文件拷贝到 boost 主目录下,执行 ./bjam

调用 build system ,指定 编译工具 来安装,比如说, GNU/GCC

bjam "-sTOOLS=gcc" install

如果你只打算生成库 并且把编译好的库收集到一个文件夹里面而不打算安装它们。

bjam "-sTOOLS=gcc" stage

 

bjam [options...] [install|stage]

--libdir=DIR

安装库的目标文件夹。
默认: EPREFIX/lib

--includedir=DIR

安装代码头文件的文件夹, Boost 头文件夹将会安装在定义的文件夹的 "boost-<version>" 里面。
默认: PREFIX/include

--builddir=DIR

指定使用的生成文件夹 ,这将编译代码时产生的中间文件 放到一个指定的路径下而不用和代码混在一起。 推荐!

--stagedir=DIR

如果只是使用 state 生成库文件 ,这个变量指定这些库将会被放在哪里。
默认 ; ./stage

 

./bjam -sPYTHON_ROOT=/usr -sPYTHON_VERSION=2.3 -sTOOLS=gcc --builddir=Build stage  只生成库,并不安装。

./bjam -sPYTHON_ROOT=/usr -sPYTHON_VERSION=2.3 -sTOOLS=gcc --builddir=Build 安装在默认路径下,中间文件放在 Build 文件夹下。

注意:安装过程会自动获取 Python 的版本,没有需要自己填写版本。两外 regex 库的安装需要 Unicode/ICU ,如果没有安装,会自动跳过,可忽略。

编译的时间比较长,大约需要 45 分钟

编译结果:生成相关的库文件。有 date_time filesystem iostreams program_options   python regex serialization signals test thread wave 等库文件,这些库和系统相关,并非完全模板实现。

 

文件结构( boost 源代码目录)

boost  ----  boost 的源代码目录,也就是核心功能代码,基本上都是模板实现!

libs   ----  boost 相应库的使用范例和测试代码。从使用 boost 的角度看,有很高的参考价值!

Tools  ----  boost 库相关的工具,比如 build 工具。可以不看。

Doc/more/people/wiki 等目录,含有和 boost 相关的信息,如使用说明( XML 格式),作者信息等。没必要看。

 

 

Bind 库学习

~/libs/bind/test/ bind_test.cpp 是一个很好的参考。)

Bind 库用于创建绑定到函数(自由函数或成员函数)的函数对象 ,也可以绑定类的成员变量。

参数:可以直接为创建的函数对象提供参数(内部括号 ),也可以通过 bind 外部括号提供参数 ,再使用 bind 的占位符间接给生成的函数对象提供参数。唯一需要保证的就是,提供给函数对象的参数必须和该函数的参数个数和类型一致(内部括号);通过外部括号传递的参数没有什么限制,但是这些参数的位置和占位符 1_,2_ 等有对应关系。

生成函数对象的返回值:对于绑定自由函数和成员函数, bind 库可以自动推断函数的返回值,但是对于绑定函数对象,需要指定返回值,使用 bind<type>(  ).

使用举例:

绑定自由函数:

#include <boost/bind.hpp>

#include <boost/ref.hpp>

using namespace boost;

 

long f_1(long a)

{    return a;  }

 

long f_2(long a, long b)

{    return a + 10 * b;  }

      

int const i = 1;

BOOST_TEST( bind(f_1, _1)(i) == 1L );  // 通过占位符提供参数

BOOST_TEST( bind(f_2, _1, 2 )(i) == 21L );  // 通过占位符提供参数,直接提供参数

 

绑定成员变量(包括虚函数): bind 表达式的第一个参数必须是成员函数对应类的一个实例。因为类是不具有地址的,只要类实例才有,绑定是依赖于地址的。

struct X

{

    mutable unsigned int hash;

    X(): hash(0) {}

 

    int f0() { f1(17); return 0; }

    int g0() const { g1(17); return 0; }

 

    int f1(int a1) { hash = (hash * 17041 + a1) % 32768; return 0; }

    int g1(int a1) const { hash = (hash * 17041 + a1 * 2) % 32768; return 0; }

}  

  // 1

X x;   // 类实例,赋予成员函数和成员变量的地址,用于绑定。

bind(&X::f1, &x, 1)();  

bind(&X::f1, ref(x), 1)();

 

bind(&X::g1, &x, 1)();  // 使用对象指针,拷贝指针而已

bind(&X::g1, x, 1)();    // 使用值传递,会引起对象拷贝

bind(&X::g1, ref(x), 1)();   // 使用对象引用,可以避免复制

bind(&X::g1,cref(x), 1)();   // 使用对象 const 引用,可以避免复制

// 传递对象的值会产生拷贝,从而不用考虑原先对象的生存期,但是为引起性能消耗;可以通过传递指针,或者显式指定传递引用 (ref()/cref()) 来避免对象拷贝。这条准则也使用于需要以对象作为参数的 lambda 表达式等。

 

绑定函数对象

struct Y

{

    short operator()(short & r) const { return ++r; }

    int operator()(int a, int b) const { return a + 10 * b; }

    long operator() (long a, long b, long c) const { return a + 10 * b + 100 * c; }

    void operator() (long a, long b, long c, long d) const { global_result = a + 10 * b + 100 * c + 1000 * d; }

};

 

void function_object_test()

{

    using namespace boost;

    short i(6);

    int const k = 3;

 

    BOOST_TEST( bind<short> (Y(), ref(i) )() == 7 );  // 传递变量 i 的引用

    BOOST_TEST( bind<short>(Y(), ref(i))() == 8 );

    BOOST_TEST( bind<int>(Y(), i, _1)(k) == 38 );

    BOOST_TEST( bind<long>(Y(), i, _1, 9)(k) == 938 );

}

/// 绑定适配的函数对象,需要定义 result_type 来完成类型推断( 返回值)。

struct Z

{

    typedef int result_type;    // 关键点

    int operator()(int a, int b) const { return a + 10 * b; }

};

 

void adaptable_function_object_test()

{

    BOOST_TEST( boost::bind(Z(), 7, 4)() == 47 );

}

 

组合绑定:内层绑定先求值,所得的返回值作为外层 bind 生成函数对象的参数。

    int const x = 1;

    int const y = 2;

    BOOST_TEST( bind(f_1, bind(f_1, _1))(x) == 1L );

    BOOST_TEST( bind(f_1, bind(f_2, _1, _2))(x, y) == 21L );

    BOOST_TEST( bind(f_2, bind(f_1, _1), bind(f_1, _1))(x) == 11L );

    BOOST_TEST( bind(f_2, bind(f_1, _1), bind(f_1, _2))(x, y) == 21L );

 

 

Lambda 库学习

Lambda 库能够创建可以直接定义和调用的函数对象 ,或者把函数对象存储起来以便于后续调用。即在调用点定义未命名函数。其功能涵盖 bind 库。

Lambda bind 功能和 bind 库功能基本一样 ,不再赘述。

创建 Lambda 表达式时没有关键字或者名称,由占位符表示这里是一个 lambda 表达式。

如果要使常量编程 lambda 表达式的一部分,需要使用 constant 关键字。

 

基本用法

lambda 支持所有内置类型的运算,运算规则不变,同原先的算术、逻辑、移位、比较等操作。

boost::function<int (int, int)> f;  // 定义一个回调函数

f = _1 + _2;                  // 定义一个 lambda 函数对象,并存储在 f

BOOST_CHECK(f(1, 2)== 3);  // 使用 lambda 函数

 

  int i = 1; int j = 2; int k = 3;

  using namespace std;

  using namespace boost::lambda;

  BOOST_CHECK((_1 + 1)(i)==2);     // 对表达式求值,不改变变量值

  BOOST_CHECK(((_1 + 1) * _2)(i, j)==4);

  BOOST_CHECK((_1 - 1)(i)==0);

 

    i = 1;

  (i += _1)(make_const(1));  // 对表达式求值,并赋值给变量 i

  BOOST_CHECK(i == 2);

 

bool t = true, f = false;

  BOOST_CHECK((_1 && _2)(t, t) == true);  // 对表达式求值

 

  int i = 0;

  BOOST_CHECK(_1++(i) == 0);   // 变量 i 的自家运算

  BOOST_CHECK(i == 1);

 

常见用途: 1 无需 <functional> 的算术运算; 2 编写可读的谓词

std::vector<int>  vec;

…………….

std::for_each(vec.begin(), vec.end(), _1+=10);   // 算术运算

std::for_each(vec.begin(), vec.end(), _1*=2);

 

std::find_if(vec.begin(), vec.end(),  (_1>3 && _1<10) || _1<1);

std::find_if(vec.begin(), vec.end(),  _1>=4 && _1<10);  // 可读谓词

 

lambda Bind 功能同 bind 库,此外, lambda 库还具有控制结构,转型、构造和析构、异常等不常用的功能,需要时参考 libs 中的相关代码和例子。

 

Function 库学习

Function 主要用于为后续的调用存储函数的指针和函数对象。传统上,函数指针 用于实现回调函数和延迟函数 Function 库采用范型机制来定义所要存储的函数的签名 ,而让调用者来决定提供哪一种类型的类函数实体 (函数指针或函数对象)。因此 Function 可以完全实现函数指针所能的功能,并可以给存储的函数增加状态 (一般函数是没有状态的,但是函数对象本质是类,故可以存储对象)。(定义了: return_type operator() ( 参数 ) 的类,即为函数对象。

 

基本功能:

#include <iostream>

#include “boost/function.hpp”

 

bool some_func(int i, double d)

{ return i>d; }

 

int main(){

boost::function<bool (int, double) > f;   // 声明

f = &some_func;    // 一般使用函数指针,默认函数名也是函数指针

f(10,1.1);          // 延迟调用,或者回调

}

 

struct write_five_obj { void operator()() const { global_int = 5; } };

  typedef function<void ()> func_void_type;  // 必须和函数对象的 operator 对应。

 

  write_five_obj five;

  func_void_type v1;

  BOOST_CHECK(v1.empty());

  // Assignment to an empty function

  v1 = five;    // 使用函数对象,会产生拷贝

  BOOST_CHECK(v1 != 0);

// 存储类成员函数, function 的第一个参数必须为类的实例 。原因同 bind function 本质上依赖于函数的地址,只要类的实例才有地址,因此必须将所要存储的类成员函数的所在实例传递给 function 变量。

struct X {

  X(int v) : value(v) {}

 

  int twice() const { return 2*value; }

  int plus(int v) { return value + v; }

 

  int value;

};

 

static void

test_member_functions()

{

  boost::function<int (X*)> f1(&X::twice);

 

  X one(1);

  X five(5);

  BOOST_CHECK(f1(&one) == 2);

  BOOST_CHECK(f1(&five) == 10);

 

  boost::function<int (X*)> f1_2;

  f1_2 = &X::twice;

  BOOST_CHECK(f1_2(&one) == 2);

  BOOST_CHECK(f1_2(&five) == 10);

 

  boost::function<int (X&, int)> f2(&X::plus);

  BOOST_CHECK(f2(one, 3) == 4);

  BOOST_CHECK(f2(five, 4) == 9);

}

 

 

Signals 库学习

Signals 库具体化信号( signal )和插槽( slot ),信号指的是某种可以“发射”的东西,而插槽指的是接收信号的连接。这是一种著名的设计模式,称为观察着、信号 / 插槽,发布者 / 订阅者、事件等。该设计模式常常用于代码的解耦。

注意:

1.         signal 永远不能复制。因此包含 signal 的类必须手动编写复制构造函数和复制运算符。

2.         属于同一个组的插槽会以不确定的顺序执行,如果插槽的调用顺序事关紧要,就必须把插槽放入不同的组中。

3.         signal 类模板拥有一个名为 Combiner 参数后类型,它就是负责组合并返回结果的类型。默认返回所调用的最后一个插槽的返回值。但是可以通过自定义的 Combiner 的函数对象来组合返回值。

4.         boost::signal::connect 会返回一个 boost::signals::connection 的实例。通过使用这个 connection 对戏那个可以将插槽和 signal 断开,还可以测试插槽是否已经连接到 signal Connection signal 和插槽之间的实际链接句柄。

 

Signal 库的使用:定义一个 signal 对象,声明同 function 。定义自由函数或者函数对象。函数签名同 signal 所声明的格式。在这样的基础上,就可以将自由函数指针、函数对象、 bind 表达式或者 lambda 表达式作为插槽 signal 对象相连使用 connect 方法,建立插槽和信号的关联。 connect 可以对插槽分组 ,确定插槽的调用顺序。之后只要,调用 signal 对象的 operator()(T1,T2,T3,…,TN) 方法发射信号 ,相关联的插槽就会被回调。传递给插槽的参数通过 operator() 传递

 

如何将 signals 库用于类之间的通讯。 signal 对象作为某个类的成员变量 ,并对外提供建立链接的接口。作为插槽使用的类,应该定义为函数对象,即定义 operator() 接口 。使用中,只要触发 signal 对象发送信号,相应插槽类的 operator() 接口将会被调用。

g++ -o test doc_view.cpp  -lboost_signals-gcc-d

 

举例:

#include <boost/bind.hpp>

#include <boost/signals/signal1.hpp>

#include <iostream>

 

struct print_string : public boost::signals::trackable

{

        typedef void result_type;

        void print(const std::string& s) const { std::cout << s << std::endl; }

};

 

struct my_button

{

        typedef boost::signal1<void, const std::string&> click_signal_type;

        typedef click_signal_type::slot_type click_slot_type;

 

        boost::signals::connection on_click_connect(const click_slot_type& s)

        { return on_click.connect(s); }

 

        my_button(const std::string& l) : label(l) {}

 

        virtual ~my_button() {}

 

        void click();

 

        protected:

        virtual void clicked() { on_click(label); }

 

        private:

        std::string label;

        click_signal_type on_click;

};

 

void my_button::click()

{

        clicked();

}

 

int main()

{

        my_button* b = new my_button("OK!");

        print_string* ps = new print_string();

        b->on_click_connect(boost::bind(&print_string::print, ps, _1));  // 使用 bind 表达式作为插槽。

 

        b->click(); // prints OK!

        delete ps;

        b->click(); // prints nothing

        return 0;

}

 

上述四个库之间的联系:

lambda 表达式用于生成临时的函数对象,也称未命名函数。 Bind lambda 的功能子集,同样可以生成函数对象,但是不能仅仅通过占位符来生成临时函数对象,需要依赖于现有的函数指针、类(成员变量、成员函数)才能生成函数对象。

Function 用于存储函数指针和函数对象,用于后续的调用,同时屏蔽函数指针和函数对象间的区别,更好的支持范型编程。

Signal 是一种特定设计模式的实现,更好的解除代码(类)之间的耦合关系。 Signal 通过声明是采用的函数签名,即可以使用 connect 方法将满足签名条件的函数指针、函数对象、 bind 表达式或 lambda 表达式作为插槽与信号对象关联。

总得来说,这四个类都和函数指针和函数对象有关,因此都依赖于于对象实际的地址。由于函数对象本质是类的实例,具有状态,因此相当于在函数中保存状态,这在很多时候有用。其中 bind 表达式和 lambda 表达式都是产生函数对象,只是该函数对象事先没有声明而已,这在使用 STL 的算法时非常有用,可以简化代码,提高可读性。由于 bind 表达式和 lambda 表达式本质就是临时的函数对象,因此也可以在 Function signal 中使用。

 

 

智能指针 shared_ptr

boost shared_ptr 是一个引用计数的智能指针,提供了比 auto_ptr 更好的功能,允许多个客户之间共享对象。可用在下面情形中:

1.         当对象拥有多个客户,但没用明确的拥有者时;

2.         当在标准库容器中存储指针时;

3.         当在库之间来回传递对象而没有表明所有权时;

4.         当管理一些需要特殊清楚操作(自定义删除工具)的资源时。

 

Conversion

polymorphic_cast 用于多态转换,并且不管是对引用类型还是对指针类型进行转换时只要转换失败,都会抛异常。对于 STL dynamic_cast ,如果转换引用类型,那么会通过抛异常来报告转换失败;如果转换指针类型,则是通过返回空指针来报告转换失败。它们都可以用于从基类向派生类的向下转型和交叉转型 (crosscast) ,即从一个基类到另一个基类的转型(多继承情况)。 polymorphic_cas 行为一致,比较不容易出错。

Polymorphic_downcast static_cast 的替代版本,因为 polymorphic_cas 在调式状态下使用 dynamic_cast 来测试是否失败。在发布模式下,两者都是很不安全的。因为两者都是仅仅执行所需的指针算术运算,而把保证转换有效的责任留给程序员,转换永远不会出错,但却不保证转换后的指针有效(段错误)。最好不用!

numeric_cast 函数提供了算术类型之间的高效的、进行范围检查的转换。可用于以下情况:

1.         需要进行无符号和又符合类型之间的赋值或比较时;

2.         需要进行不同大小的整数类型之间的赋值或比较时;

建议不要使用,因为最好的方法就是不要转换!

Lexical_cast 函数 是一个用于字符串类型其他类型 之间的进行词法转换的可重用的而且高效的工具。使用 Lexical_cast 函数的动机有以下几种情况:

1.         从字符串类型到数值类型的转换

2.         从数值类型到字符串类型的转换;

3.         用户自定义类型所支持的所有的词法转换。

Lexical_cast 函数实现上依赖于 STL::stringstream 函数,因此该函数可用于任何支持 operator<< 进行输出的源和任何支持 operator>> 进行输入的目标。对于内置类型无需改变,对于用户定义的类型 (user-defined type, UDT) 则需要子定义 >> << 操作,比如:

friend std::ostream operator<<(std::ostream& o, const Type& t);

friend std::istream operator>>(std::istream& i, Type& t);

 

 

 

Utility 库学习

提供编译时断言 BOOST_STATIC_ASSERT ,当然条件必须编译时可知,如编译常量和类型信息, sizeof 函数。常用于范型编程。

提供安全的析构函数 checked_delete checked_array_delete 。判断析构函数是否完整。

提供禁止复制类 noncopyable ,继承该类的派生类不能被赋值和复制。简化对类的复制构造函数和复制赋值运算符的禁止代码编写。

提供 addressof 函数获取任何对象的真实地址,即使重载了 operator& 。防止恶意重载。

通过 enable_if disable_if 控制重载和特化的参与。也可用于范型编程。

 

 

Operators 库学习

为用户自定义的类提供一组关系正确的关系运算符和算术运算符是很重要的,在使用某些 STL 算法是还是必须的。 Operators 提供相应功能,看大大简化该任务,并更好的保证正确性和对称性。在决定为类增加运算符是一定要深思熟虑,但是一旦决定了要为类增加运算符,则应尽可能使用 operators 库。

 

 

Regex 库学习

Regex 库为 C++ 提供正则表达式的支持,将被纳入 STL ,主要用于文本处理和验证输入。

Boost/regex.hpp

Typedef  basic_regex<char>  regex;   // 类似 string

Typedef  basic_regex<wchar>  wregex;

从某种意义上来说, regex 是一个特殊类型的字符串的容器,表示特定的字符串模式。

用于 regex 类的自由函数: regex_match/regex_search/regex_replace.

regex_match 用于判断正则表达式是否完全匹配整个字符串序列。

regex_search 在字符串序列中查找匹配正则表达式的子序列。

regex_replace 用特定的子序列替换匹配正则表达式的子序列。

 

参考 ~/libs/reges/ 学习。对于 regex 表达式的语法,可以参考《精通正则表达式 3 》。

 

 

Any 库学习

       一般类型安全可以防止犯错,并且改善代码的性能,应该尽可能避免使用无差别类型。但是如果需要异构存储、需要将使用者与类型的细节隔离、或者需要在较低的层次获得最大的灵活性,那么可以考虑使用 any 库。该库可以对任意的类型提供类型安全的存储和安全的检索;提供了在标准库容器中存放异构类型的方法(使用 any 类型);传递类型所通过的层无须了解任何该类型的信息。

       自由函数: any_cast dynamic_cast 一样 ,对引用转型会抛异常报错;对指针转型则通过返回空指针报错。

       boost::any a;  a=std::string(“agadgadh”);   a= 42;     a= 3.1416;

       std::string s = boost::any_cast<std::string>(a);  // throw bad_any_cast

       double i = boost::any_cast<double>(a);   // 返回正确值

       int * k = boost::any_cast<int>(a);   // 返回空指针