Boost.bind 好用么?当然好用,而且它也确定进入下一代的 C++ 标准了,也早就进了 TR1 了。回顾一下,它允许我们干这个:
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
struct Person
{
Person(const string& name)
: name_(name)
{}
string Name()
{
return name_;
}
string name_;
};
int main()
{
typedef vector<Person> 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 。
<stockticker><span lang="EN-US" style="font-family: Arial;">STL</span></stockticker> 的 bind1st 能给一点启示:它接受两个参数,返回一个 functor ,毫无疑问 simple_bind 也得这样干,我们把它返回的 functor 类型叫做 simple_binder:
template <typename R, typename T>
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 <typename R, typename T>
simple_binder<R, T>
simple_bind( R (T::*pfn)(), ??? )
{
return simple_binder<R, T>(pfn);
}
有没有发现什么?在这里我们压根没用到第二个参数,这就给了我们一个极其简单的定义:
class placeholder{};
placeholder _1;
那么现在程序好像是完整了,我们可以这么用:
#include <iostream>
#include <string>
using namespace std;
struct Person
{
Person(const string& name)
: name_(name)
{}
string Name()
{
return name_;
}
string name_;
};
template <typename R, typename T>
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 <typename R, typename T>
simple_binder<R, T>
simple_bind( R (T::*pfn)(), placeholder&)
{
return simple_binder<R, T>(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 <typename R, typename T, typename Arg>
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 直接喂给 <stockticker><span lang="EN-US"><span style="font-family: Arial;">STL</span></span></stockticker> 算法:
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Person
{
Person(const string& name)
: name_(name)
{}
string Name()
{
return name_;
}
void SetName(string name)
{
name_ = name;
}
string name_;
};
template <typename R, typename T, typename Arg>
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 <typename R, typename T, typename Arg>
simple_binder<R, T, Arg>
simple_bind( R (T::*pfn)(Arg), const Arg& arg, placeholder&)
{
return simple_binder<R, T, Arg>(pfn, arg);
}
int main()
{
typedef vector<Person> 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 <typename R, typename T, typename Arg>
simple_binder<R, T, Arg>
simple_bind( R (T::*pfn)(Arg), const placeholder&, const Arg& arg)
{
return simple_binder<R, T, Arg>(pfn, arg);
}
加上这个,_1 就可以被随便放在第一个或者第二个位置,例如:
int main()
{
typedef vector<Person> 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 <typename R, typename T, typename Arg>
simple_binder<R, T, Arg>
simple_bind( R (T::*pfn)(Arg), const placeholder&, const placeholder&)
{
return simple_binder<R, T, Arg>(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 <algorithm>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Person
{
Person(const string& name)
: name_(name)
{}
string Name()
{
return name_;
}
void SetName(string name)
{
name_ = name;
}
string name_;
};
template <typename R, typename T, typename Arg>
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 <typename R, typename T, typename Arg>
simple_binder<R, T, Arg>
simple_bind( R (T::*pfn)(Arg), const Arg& arg, const placeholder&)
{
return simple_binder<R, T, Arg>(pfn, arg);
}
template <typename R, typename T, typename Arg>
simple_binder<R, T, Arg>
simple_bind( R (T::*pfn)(Arg), const placeholder&, const Arg& arg)
{
return simple_binder<R, T, Arg>(pfn, arg);
}
template <typename R, typename T, typename Arg>
simple_binder<R, T, Arg>
simple_bind( R (T::*pfn)(Arg), const placeholder&, const placeholder&)
{
return simple_binder<R, T, Arg>(pfn);
}
int main()
{
typedef vector<Person> 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;
然而模仿 <stockticker><span lang="EN-US"><span style="font-family: Arial;">MCD</span></span></stockticker> 的 IntToType 技术,这样是不是更优雅呢:
template <int I>
class placeholder{};
placeholder<1> _1;
placeholder<2> _2;
boost.bind 里面就是这么干的。有了这个,就可以依照 placeholder 顺序来重载:
template <typename R, typename T, typename Arg>
simple_binder<R, T, Arg>
simple_bind( R (T::*pfn)(Arg), const placeholder<1>&, const placeholder<2>&)
{
return simple_binder<R, T, Arg>(pfn);
}
template <typename R, typename T, typename Arg>
simple_binder<R, T, Arg>
simple_bind( R (T::*pfn)(Arg), const placeholder<2>&, const placeholder<1>&)
{
// 返回什么呢?
}
返回什么呢?我们可以定义一个 simple_binder2 ,它在 operator() 里面会交换其参数的位置,也可以修改 simple_binder,让它根据某些标志来干这件事……
我们还是就此打住吧!
不用我说你也看得出来,这种“暴力法”很 快就会让代码规模和程序复杂度膨胀到无法收拾的地步,是时候检讨我们的设计了。我们原先的设计其实是从最简单的情况出发,在需求增多的时候,我们用蛮力加 以扩展,然而当规模进一步扩大,设计本身的局限就开始显现出来,现在应该从原先的设计中提取抽象,考虑更加灵活的设计了。
(待续...)