社区应用 最新帖子 精华区 社区服务 会员列表 统计排行 银行

  • 2080阅读
  • 1回复

Boost Phoenix库介绍

级别: 管理员
发帖
6979
金币
2566
威望
3026
贡献值
0
元宝
0
Phoenix 可以看作是对 Boost Lambda Library 的重新实现。它是作为 Boost.Spirit 的一部分的,但是我们完全可以把它作为单独的库来使用,以达到一些非常酷和方便的效果。

基本上,Phoenix 大大方便了在 C++ 中进行 functional programming。当然,由于 C++ 不是函数式语言,我们没有可能进行严格的 functional programming,库的作用也只是让这种风格得到更方便的实现。在 STL 里,我们已经在进行一些 FP,例如对 functor 的利用使得算法的重用性大大提高,而 Phoenix 则让编写 functor 更加自然和简便。

val
在下面的代码中,val(3) 和 val("Hello Phoenix") 都是 functor,它们的作用就是返回它们的参数。而且,它们是 lazily evaluated 的。

[cpp] view plaincopy
#include <iostream>  
#include <boost/spirit/include/phoenix_core.hpp>  
using namespace std;  
using namespace boost::phoenix;  
int main()  
{  
    cout << val(3)() << endl;  
    cout << val("Hello Phoenix")() << endl;  
    return 0;  
}  

输出:
3
Hello Phoenix

ref
val 返回的是对参数的复制,但有时候我们需要返回的是引用,这时可以使用 ref:

[cpp] view plaincopy
#include <iostream>  
#include <boost/spirit/include/phoenix_core.hpp>  
using namespace boost::phoenix;  
using namespace std;  
int main()  
{  
    int i = 3;  
    char const* s = "Hello Phoenix";  
    cout << ref(i)() << endl;  
    cout << ref(s)() << endl;  
    return 0;  
}  

输出与上面相同。

使用 lazy functor
这两个例子显得有点太无趣了,lazy evalution 的好处在于,一个 functor 可以被保存起来以后再用,因此也可以“插入”到已有的算法当中。所以,如果你写了一个 print 算法,那这些 functor 就有用了:

[cpp] view plaincopy
#include <iostream>  
#include <boost/spirit/include/phoenix_core.hpp>  
using namespace std;  
using namespace boost::phoenix;  
template <typename F>  
void print(F f)  
{  
    cout << f() << endl;  
}  
int main()  
{  
    print(val(3));  
    print(val("Hello Phoenix"));  
    return 0;  
}  
  
输出结果与上面相同。

arg
在 Boost Lambda Library 中, 有几个叫做 _1, _2, _3... 之类的好东西,它们可以代替第 n 个参数,例如 _2 就返回第2个参数。Phoenix 原本也是这么命名的,但是现在改成了 arg1, arg2, arg3...,它们都位于 boost::phoenix::arg_names 命名空间。看下面程序:

[cpp] view plaincopy
#include <iostream>  
#include <boost/spirit/include/phoenix_core.hpp>  
using namespace boost::phoenix;  
using namespace boost::phoenix::arg_names;  
using namespace std;  
int main()  
{  
    int i = 3;  
    char const* s = "Hello Phoenix";  
    cout << arg1(i) << endl;  
    cout << arg2(i, s) << endl;  
    return 0;  
}  

输出仍然和上面相同。

来点有用的
下面这个例子比较有用,它去掉一个 vector 中所有的奇数,并且把结果打印出来。其中 remove_if 和 for_each 都是 STL 算法,它们都接受 functor,可以看到 Phoenix 让编写这些 functor 变得非常容易且直观。

[cpp] view plaincopy
#include <vector>  
#include <algorithm>  
#include <iostream>  
#include <boost/spirit/include/phoenix_core.hpp>  
#include <boost/spirit/include/phoenix_operator.hpp>  
using namespace boost::phoenix;  
using namespace boost::phoenix::arg_names;  
using namespace std;  
int main()  
{  
    int init[] = {2, 10, 4, 5, 6, 1, 3, 9, 8, 7};  
    vector<int> c(init, init + 10);  
    typedef vector<int>::iterator iterator;  
    iterator cend = remove_if(c.begin(), c.end(), arg1 % 2 == 1);  
    for_each(c.begin(), cend, cout << arg1 << " ");  
    return 0;  
}  

输出:
2 10 4 6 8

相关话题

关键词: boost c++ 编程
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638 欢迎加入本论坛超级QQ群:122538123
级别: 管理员
发帖
6979
金币
2566
威望
3026
贡献值
0
元宝
0
只看该作者 沙发  发表于: 01-19
Phoenix 库有着优雅的设计,它的11个模块分为4层,模块的功能之间完全正交,没有循环依赖。低层模块不依赖高层模块,同一层的模块之间也不互相依赖。这意味着用户可以自由选用自己需要的模块,而不需要把其余部分包含进来。

Core
位于 Phoenix 库最底层(第一层)的是 Actor 模块,它是对于 Phoenix functor 的原型定义,做了一个 functor 最基本的工作:重载 operator()。
Actor 分为两种:Primitives 和 Composites,这两个模块构成 Phoenix 库的第二层。第一层和第二层称为 Phoenix 的 Core。

Primitives 提供了 Phoenix 库的功能“砖头”,而 Composites 则是“水泥”,把砖头粘接起来形成更大的功能块。

功能模块
Primitives 包括3种:Function,Operator 和 Statement。
Function --- 用来支持 Lazy functions (例如 add)
Operator --- 用来支持 Lazy operators (例如 +)
Statement --- 用来支持 Lazy statements (例如 if_, while_)
Composites 也包括3种:Object,Scope 和 Bind。
Object --- 支持 Lazy cast(例如 static_cast_)以及对象的创建和销毁(new_ 和 delete_)
Scope --- 支持作用域,本地变量和 lambda-lambda
Bind --- 有点类似 boost::bind,用来把普通函数,成员函数和成员变量包装成 lazy functions

以上的6个模块位于 Phoenix 的第三层

"Lazy" STL
在 Phoenix 的最高层(第四层),是两个模仿 STL 的模块:Container(也称为 Intrinsic)和 Algorithm。
Container 提供的不是容器,而是容器的方法的 lazy 版本,例如 push_back
Algorithm 就是 STL 算法的 lazy 版本


包含文件
Core   #include <boost/spirit/home/phoenix/core.hpp>
Function   #include <boost/spirit/home/phoenix/function.hpp>
Operator   #include <boost/spirit/home/phoenix/operator.hpp>
Statement   #include <boost/spirit/home/phoenix/statement.hpp>

Object   #include <boost/spirit/home/phoenix/object.hpp>
Scope   #include <boost/spirit/home/phoenix/scope.hpp>
Bind   #include <boost/spirit/home/phoenix/bind.hpp>
Container   #include <boost/spirit/home/phoenix/container.hpp>
Algorithm   #include <boost/spirit/home/phoenix/algorithm.hpp>

Actor
Actor 是整个 Phoenix 的核心,几乎所有的组件事实上都是 Actor。但是这个处于中心地位的组件,其实是一个很简单的 concept:Actor 就是一个functor,它接受0~N个参数。
[cpp] view plaincopy
template <typename Eval>  
struct actor : Eval  
{  
    return_type  
    operator()() const;  
    template <typename T0>  
    return_type  
    operator()(T0& _0) const;  
    template <typename T0, typename T1>  
    return_type  
    operator()(T0& _0, T1& _1) const;  
    //...  
};  

actor 的模板参数,Eval,给 actor 提供了特例化的行为。下面我们会看到,其余的 Phoenix 组件是如何在 actor 的基础上,建立起一座优美的建筑的。
Arguments

#include <boost/spirit/home/phoenix/core/argument.hpp>

Phoenix 的 Arguments 就是占位符,它们就像一个个数据“桶”,我们可以把数据放在里面,当作函数的参数来传递。它们的类型其实是:
actor<argument<N> >
N 代表第 N 个参数。Phoenix 已经预定义了几个参数占位符:
actor<argument<0> > const arg1 = argument<0>();
actor<argument<1> > const arg2 = argument<1>();
actor<argument<2> > const arg3 = argument<2>();
......
Values

#include <boost/spirit/home/phoenix/core/value.hpp>

Phoenix 的 value 是用来包装常数的,当我们把一个常数6传递给一个 lazy function 的时候,它实际是包装在一个 actor<value<int> > 对象里面。所有 value 的类型都是
actor<value<T> >
其中 T 自然是常数的类型,在第一篇的例子中,已经使用过类似 val(3) 这样的表达式,实际上 val 就会产生一个 actor<value<T> > 对象。
References

#include <boost/spirit/home/phoenix/core/reference.hpp>

Value 是包装常量的,如果要包装变量的引用,就要用到 reference,不用说,它们的类型是
actor<reference<T> >
T 仍然是变量的类型。这个模块在前面没有详细的例子,请看下面代码:
[cpp] view plaincopy
#include <iostream>  
#include <vector>  
#include <algorithm>  
#include <boost/spirit/home/phoenix/core.hpp>  
#include <boost/spirit/home/phoenix/operator.hpp>  
using namespace std;  
using namespace boost::phoenix;  
using namespace boost::phoenix::arg_names;  
int main()  
{  
    vector<int> v(10);  
    int i = 1;  
    std::generate(v.begin(), v.end(), ref(i)++);  
    std::for_each(v.begin(), v.end(), cout << arg1 << " ");  
    return 0;  
}  

输出:
1 2 3 4 5 6 7 8 9 10
在 std::generate(v.begin(), v.end(), ref(i)++) 这一行,每一次 ref(i)++ 被调用时,它除了返回 i 的当前引用,还真正对 i 的引用做自增操作,这样 i 的值会不断增加,从而把个等差数列填入 v。


Function

#include <boost/spirit/home/phoenix/function/function.hpp>

Phoenix 中的 function 可不是 boost 的泛型函数指针,它是用来帮助你实现 lazy function 的。把一个自己的函数实现成 lazy function 的主要好处就是,它可以和 Phoenix 的其它模块和谐共处。这个我们先按下不表,以后用到的时候,再详细解释。

Operator

#include <boost/spirit/home/phoenix/operator.hpp>

这个模块重载了几乎所有可以重载的 C++ 操作符,提供的当然是它们的 lazy 版本。事实上,正是由于有了这些 lazy operators,上一篇中这样的代码才成为可能:
std::generate(v.begin(), v.end(), ref(i)++)
std::for_each(v.begin(), v.end(), cout << arg1 << " ");  
其中的 ++, << 都被解析为重载后的 lazy 版本。

Statement

#include <boost/spirit/home/phoenix/statement.hpp>

这个模块非常有趣,它提供了许多 C++ 语句,例如 if, else, while, switch, 甚至 try...catch 的 lazy 版本。可以说,它是在用 C++ 实现一个 lazy 版本的 C++!
这样说来比较抽象,我们看看一个例子:
[cpp] view plaincopy
#include <algorithm>  
#include <boost/spirit/home/phoenix/core.hpp>  
#include <boost/spirit/home/phoenix/operator.hpp>  
#include <boost/spirit/home/phoenix/bind.hpp>  
#include <boost/spirit/home/phoenix/statement.hpp>  
using namespace std;  
using namespace boost::phoenix;  
using namespace boost::phoenix::arg_names;  
struct Person  
{  
    string name;  
    uint age;  
    Person() {}  
    Person(string const& _name, uint _age)  
        : name(_name)  
        , age(_age)  
    {}  
    void greet()  
    { cout << "Hello, " << name << endl; }  
    void greet(string const& _s)  
    { cout << _s << name << endl; }  
};  
int main()  
{  
    typedef vector<Person> PersonList;  
    PersonList pl;  
    pl.push_back(Person("Amy", 20));  
    pl.push_back(Person("Rose", 21));  
    pl.push_back(Person("Ralph", 30));  
    pl.push_back(Person("Zoe", 50));  
    std::for_each(pl.begin(), pl.end(),  
        if_(bind(&Person::age, arg1) < 25)  
        [  
            bind(&Person::greet, arg1, "Hi, young ")  
        ].  
        else_  
        [  
            bind(&Person::greet, arg1)  
        ]  
    );  
    std::for_each(pl.begin(), pl.end(),  
        switch_(bind(&Person::age, arg1))  
        [  
            case_<30>(cout << bind(&Person::name, arg1) << ", you stand by yourself." << endl),  
            case_<50>(cout << bind(&Person::name, arg1) << ", you know your fate." << endl),  
            default_(cout << bind(&Person::name, arg1) << ", enjoy your life!" << endl)  
        ]  
    );  
    return 0;  
}  

输出:
Hi, young Amy
Hi, young Rose
Hello, Ralph
Hello, Zoe
Amy, enjoy your life!
Rose, enjoy your life!
Ralph, you stand by yourself.
Zoe, you know your fate.
使用 if_, else_, switch_,case_,我们大大拓展了 for_each 的用途,实现了一些相当复杂的循环。我们可以想象,把这些 lazy statement 和其他的那么多 STL 算法结合起来,可以发挥多大的威力。


Object

#include <boost/spirit/home/phoenix/object.hpp>

Object 模块包括7个组件,分别对应 C++ 的构造函数,new, delete, const_cast, static_cast, dynamic_cast 和 reinterprete_cast 的 lazy 版本。
还是看个例子,如果我们想要用 vector 建立一个数字金字塔,如下面的样子,那该怎么写呢?

1
2 2
3 3 3
4 4 4 4
5 5 5 5 5
6 6 6 6 6 6
7 7 7 7 7 7 7
8 8 8 8 8 8 8 8
9 9 9 9 9 9 9 9 9
10 10 10 10 10 10 10 10 10 10
当然,写一个双层循环没任何问题,这也是大家刚学编程的时候常见的练习题,不过用 Phoenix 的 lazy constructor 结合 STL,我们可以只用一句话就搞定:
[cpp] view plaincopy
#include <iostream>  
#include <vector>  
#include <algorithm>  
#include <boost/spirit/home/phoenix/core.hpp>  
#include <boost/spirit/home/phoenix/operator.hpp>  
#include <boost/spirit/home/phoenix/object.hpp>  
#include <boost/spirit/home/phoenix/statement.hpp>  
#include <boost/spirit/home/phoenix/container.hpp>  
using namespace std;  
using namespace boost::phoenix;  
using namespace boost::phoenix::arg_names;  
int main()  
{  
    typedef vector<vector<int> > Matrix;  
    Matrix m(10);  
    int i = 1;  
    generate(m.begin(), m.end(), construct<vector<int> >(ref(i)++, ref(i)));  
    for_each(m.begin(), m.end(),  
        (  
            for_(ref(i) = 0, ref(i) < size(arg1), ++ref(i))  
            [  
                cout << arg1[ref(i)] << " "  
            ],  
            cout << val("/n")  
        )  
    );  
    return 0;  
}  


上面的代码里,用来把 Matrix m 初始化成一个金字塔的代码只有一句:
generate(m.begin(), m.end(), construct<vector<int> >(ref(i)++, ref(i)));
其中的魔法是这样的:
STL 的 generate 会在 m 的最外层循环,每一次都把 construct<vector<int> >(ref(i)++, ref(i))) 的结果赋予 m 的一个元素;
construct<vector<int> > 会在栈上构造一个 vector<int>,参数为后面两个:ref(i)++ 和 ref(i);
ref(i) 每次都返回一个自增的 i,第一个成为 vector<int> 的元素个数,第二个成为初始值。
于是,我们得到了这个数字金字塔,非常简洁。相反,我们的打印代码倒是挺长,而且利用了 lazy 的 for 语句。有没有什么简单一点的办法呢?有的,但是要等到我们讲完了 lazy container 和 lazy algorithm 再说。
Bind
Phoenix 的 bind 和 boost.bind 差不多,但是更加健全些,我们在前面的 Statement 例子中已经用到过了。这里强调一下的是,它可以成功处理重载函数,在 Statement 例子里,bind 根据后面参数的个数,自动选择了正确的 greet 重载版本。




QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638 欢迎加入本论坛超级QQ群:122538123
描述
快速回复

您目前还是游客,请 登录注册
批量上传需要先选择文件,再选择上传