Chinaunix首页 | 论坛 | 博客
  • 博客访问: 19001318
  • 博文数量: 7460
  • 博客积分: 10434
  • 博客等级: 上将
  • 技术积分: 78178
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-02 22:54
文章分类

全部博文(7460)

文章存档

2011年(1)

2009年(669)

2008年(6790)

分类: C/C++

2008-05-28 16:17:15

Boost.bind 好用么?当然好用,而且它也确定进入下一代的 C++ 标准了,也早就进了 TR1 了。回顾一下,它允许我们干这个:

#include
#include
#include
#include
#include

using namespace std;
using namespace boost;

struct Person
{
    Person(const string& name)
        : name_(name)
    {}
    
    string Name()
    {
        return name_;
    }
    
    string name_;
};

int main()
{
    typedef vector PersonList;
    
    PersonList personList;
    personList.push_back(Person("Ralph"));
    personList.push_back(Person("Joy"));
    personList.push_back(Person("Martin"));
    
    PersonList::iterator iter =
        find_if(personList.begin(), personList.end(),
            bind(&Person::Name, _1) == "Ralph");
            
    cout << (iter == personList.end() ? "Not found."
                                      : iter->Name().append(" found."))
         << endl;
}


如果没有它我们怎么办呢?恕我鲁钝,我还没办法用 bind1st, bind2nd 之类办到同样的事,恐怕你也只有写一个完全没营养的 predicate 来达到目的了。

当然,用不了几次,你就会问(或者可能多数人是在第一次看到的时候就会问):它是怎么办到的?其实还不如问,如果换了你会怎么做?我们降低条件,想象自己 bind 的作者,在开始的时候舍弃一切细节,只要实现类似 bind1st 或者 mem_fun 的功能,该怎么做?我们把这个 bind 叫做 simple_bind

STL bind1st 能给一点启示:它接受两个参数,返回一个 functor ,毫无疑问 simple_bind 也得这样干,我们把它返回的 functor 类型叫做 simple_binder

template
class simple_binder
{
public:
    explicit simple_binder(R (T::*pfn)())
        : pfn_(pfn)
    {}
   
    R operator()(T& t)
    {
        return t.*pfn_();
    }
private:
    R (T::*pfn_)();
};


这个东西跟 mem_fun_ref_t 可以相比,因为毕竟,算法是不变的,无论我们在外面玩什么花样,提供给算法的 functor 类型都必须长成这样。中间的 R (T::*pfn)() 是函数指针定义,的确,C++ 里面的函数指针定义从来就不可爱,所以才有了 boost.function ... 扯远了。

我们知道了 simple_bind 的返回类型,那么参数呢,第一个参数简单,跟上面的 pfn 是一样的,第二个是什么呢?我们先空起来:

template
simple_binder
simple_bind( R (T::*pfn)(), ??? )
{
    return simple_binder(pfn);
}


有没有发现什么?在这里我们压根没用到第二个参数,这就给了我们一个极其简单的定义:

class placeholder{};
placeholder _1;


那么现在程序好像是完整了,我们可以这么用:

#include
#include

using namespace std;

struct Person
{
    Person(const string& name)
        : name_(name)
    {}
   
    string Name()
    {
        return name_;
    }
   
    string name_;
};

template
class simple_binder
{
public:
    explicit simple_binder(R (T::*pfn)())
        : pfn_(pfn)
    {}
   
    R operator()(T& t)
    {
        return (t.*pfn_)();
    }
private:
    R (T::*pfn_)();
};

class placeholder{};
placeholder _1;


template
simple_binder
simple_bind( R (T::*pfn)(), placeholder&)
{
    return simple_binder(pfn);
}

int main()
{
    Person person("Ralph");
    cout << simple_bind(&Person::Name, _1)(person) << endl;
}


输出是 Ralph ,跟直接调用一样!我们的 simple_bind 起作用了!

好了,现在让我们再向前一步,如果 Person 加了一个函数 SetName:

    void SetName(string name)
    {
        name_ = name;
    }


它有一个参数,而我们希望调用的形式类似于 simple_bind(&Person::SetName, string("Joy"), _1)  我们怎么修改 simple_bind 呢?

首先还是从 simple_binder 入手,由于调用方法不变,我们所要做的只是在 simple_binder 里面保存参数,没错,就跟 binder1st 一样:

template
class simple_binder
{
public:
    explicit simple_binder(R (T::*pfn)(Arg), const Arg& arg)
        : pfn_(pfn)
        , arg_(arg)
    {}
   
    R operator()(T& t)
    {
        return (t.*pfn_)(arg_);
    }
private:
    R (T::*pfn_)(Arg);
    Arg arg_;
};


接下来的就顺理成章了,simple_bind arg 传递给 simple_binder ,而 placeholder 甚至不需要修改,我们甚至可以把这个 simple_bind 直接喂给 STL 算法:

#include
#include
#include
#include

using namespace std;

struct Person
{
    Person(const string& name)
        : name_(name)
    {}
   
    string Name()
    {
        return name_;
    }
   
    void SetName(string name)
    {
        name_ = name;
    }
   
    string name_;
};

template
class simple_binder
{
public:
    explicit simple_binder(R (T::*pfn)(Arg), const Arg& arg)
        : pfn_(pfn)
        , arg_(arg)
    {}
   
    R operator()(T& t)
    {
        return (t.*pfn_)(arg_);
    }
private:
    R (T::*pfn_)(Arg);
    Arg arg_;
};

class placeholder{};
placeholder _1;

template
simple_binder
simple_bind( R (T::*pfn)(Arg), const Arg& arg, placeholder&)
{
    return simple_binder(pfn, arg);
}

int main()
{
    typedef vector PersonList;
   
    PersonList personList;
    personList.push_back(Person("Ralph"));
    personList.push_back(Person("Joy"));
    personList.push_back(Person("Martin"));
   
    for_each(personList.begin(), personList.end(),
        simple_bind(&Person::SetName, string("Joy"), _1));
   
    cout << personList[0].Name() << endl
         << personList[1].Name() << endl
         << personList[2].Name() << endl;
}


输出是三个 Joy

现在看起来,我们的 simple_binder 稍微有点样子了,但是 boost.bind 允许 placeholder 被放在任何一个位置,而 simple_binder 不允许,但是这个简单,我们有重载:

template
simple_binder
simple_bind( R (T::*pfn)(Arg), const placeholder&, const Arg& arg)
{
    return simple_binder(pfn, arg);
}

加上这个,_1 就可以被随便放在第一个或者第二个位置,例如:

int main()
{
    typedef vector PersonList;
   
    PersonList personList;
    personList.push_back(Person("Ralph"));
    personList.push_back(Person("Joy"));
    personList.push_back(Person("Martin"));
   
    for_each(personList.begin(), personList.end(),
        simple_bind(&Person::SetName, string("Joy"), _1));
       
    for_each(personList.begin(), personList.end(),
        simple_bind(&Person::SetName, _1, string("Ralph")));
   
    cout << personList[0].Name() << endl
         << personList[1].Name() << endl
         << personList[2].Name() << endl;
}

输出是三个 Ralph 。提醒一下,boost.bind 当然不是用这个办法,要不然还不用等到参数数量到10个,到了3个我们就要累死。simple_bind 的目的只是在于探索,用最容易理解的方式达成目的,如果说 boost 是一座拆除了脚手架、精雕细琢的圣殿,simple_bind 就是尝试把脚手架再搭起来。

回顾一下,现在我们已经能比较好地解决一个参数的成员函数调用了,现在看看两个参数。我们从目的入手,希望达到的效果是这样:

    Person person("Ralph");
    simple_bind(&Person::SetName, _1, _2)(person, string("Martin"));

首先,这里有了一个新的符号 _2 ,这很简单

placeholder _2;

然后 simple_bind 也顺利成章的又增加了一个重载:

template
simple_binder
simple_bind( R (T::*pfn)(Arg), const placeholder&, const placeholder&)
{
    return simple_binder(pfn);
}

注意,这个 simple_binder 构造函数只有一个参数,而它还没写出来,我们加上一个:

    // new simple_binder ctor: 1 argument
    explicit simple_binder(R (T::*pfn)(Arg))
        : pfn_(pfn)
    {} 

在这种情况下,两个参数都是由调用者直接提供给 simple_binder 的,这就意味着我们还要重载一次 simple_binder::operator()

    R operator()(T& t, const Arg& arg)
    {
        return (t.*pfn_)(arg);
    }   

好了,这下子都解决了,完整程序如下:

#include
#include
#include
#include

using namespace std;

struct Person
{
    Person(const string& name)
        : name_(name)
    {}
   
    string Name()
    {
        return name_;
    }
   
    void SetName(string name)
    {
        name_ = name;
    }
   
    string name_;
};

template
class simple_binder
{
public:
    explicit simple_binder(R (T::*pfn)(Arg))
        : pfn_(pfn)
    {}
   
    explicit simple_binder(R (T::*pfn)(Arg), const Arg& arg)
        : pfn_(pfn)
        , arg_(arg)
    {}
   
    R operator()(T& t)
    {
        return (t.*pfn_)(arg_);
    }

    R operator()(T& t, const Arg& arg)
    {
        return (t.*pfn_)(arg);
    }   
private:
    R (T::*pfn_)(Arg);
    Arg arg_;
};

class placeholder{};
placeholder _1, _2;

template
simple_binder
simple_bind( R (T::*pfn)(Arg), const Arg& arg, const placeholder&)
{
    return simple_binder(pfn, arg);
}

template
simple_binder
simple_bind( R (T::*pfn)(Arg), const placeholder&, const Arg& arg)
{
    return simple_binder(pfn, arg);
}

template
simple_binder
simple_bind( R (T::*pfn)(Arg), const placeholder&, const placeholder&)
{
    return simple_binder(pfn);
}

int main()
{
    typedef vector PersonList;
   
    PersonList personList;
    personList.push_back(Person("Ralph"));
    personList.push_back(Person("Joy"));
    personList.push_back(Person("Martin"));
   
    for_each(personList.begin(), personList.end(),
        simple_bind(&Person::SetName, string("Joy"), _1));
       
    for_each(personList.begin(), personList.end(),
        simple_bind(&Person::SetName, _1, string("Ralph")));
   
    Person person("Ralph");
    simple_bind(&Person::SetName, _1, _2)(person, string("Martin"));
   
    cout << personList[0].Name() << endl
         << personList[1].Name() << endl
         << personList[2].Name() << endl;
    cout << person.Name() << endl;
}

输出是:

Ralph
Ralph
Ralph
Martin

虽然实现丑陋了点,但是达到目的了,不是么?好了,现在新的问题来了,我们还希望可以这样:

    simple_bind(&Person::SetName, _2, _1)(string("Joy"), person));

其实从这里可以看出来,在前面的实现中,placeholder 没有发挥任何作用,是真正的 placeholder ,然而在这里不同,我们至少希望 placeholder 能够携带某些编译期信息,以便让我们能在调用决策中选择相应的参数,换句话说,我们希望 placeholder _1 _2 除了名字不同,还能让编译器看起来有所区别,当然最简单的想法是:

class placeholder1{};
class placeholder2{};
placeholder1 _1;
placeholder2 _2;

然而模仿 MCD IntToType 技术,这样是不是更优雅呢:

template
class placeholder{};
placeholder<1> _1;
placeholder<2> _2;

boost.bind 里面就是这么干的。有了这个,就可以依照 placeholder 顺序来重载:

template
simple_binder
simple_bind( R (T::*pfn)(Arg), const placeholder<1>&, const placeholder<2>&)
{
    return simple_binder(pfn);
}

template
simple_binder
simple_bind( R (T::*pfn)(Arg), const placeholder<2>&, const placeholder<1>&)
{
    //
返回什么呢?
}

返回什么呢?我们可以定义一个 simple_binder2 ,它在 operator() 里面会交换其参数的位置,也可以修改 simple_binder,让它根据某些标志来干这件事……

我们还是就此打住吧!

不用我说你也看得出来,这种暴力法很 快就会让代码规模和程序复杂度膨胀到无法收拾的地步,是时候检讨我们的设计了。我们原先的设计其实是从最简单的情况出发,在需求增多的时候,我们用蛮力加以扩展,然而当规模进一步扩大,设计本身的局限就开始显现出来,现在应该从原先的设计中提取抽象,考虑更加灵活的设计了。

(待续...

阅读(267) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~