C++“准”标准库Boost学习指南(9):Boost.Bind

本文是关于C++ Boost库中的Bind组件的详细指南,它扩展了标准库的功能,允许灵活的函数和函数对象绑定,简化了参数传递和函数组合。通过Bind,你可以创建新的函数对象,改变绑定函数的arity,甚至调整参数顺序。文中通过实例展示了如何使用Bind进行简单的函数绑定和复杂的函数组合,使其成为C++编程中的强大工具。
摘要由CSDN通过智能技术生成
Boost.Bind

Bind是对标准库的绑定器bind1st 和 bind2nd的泛化。这个库支持使用统一的语法将参数绑定到任何类似于函数行为的东西,如函数指针、函数对象,以及成员函数指针。它还可以通过嵌套绑定器实现函数组合。这个库不要求那些对标准库绑定器的强制约束,最显著的就是不要求你的类提供typedefs result_type, first_argument_type, 和 second_argument_type 等。这个库也使得我们不再需要用 ptr_fun, mem_fun, 和 mem_fun_ref 等适配器。它是对C++标准库的一个重要且很有用的扩充。Bind可以被标准库的算法使用,也经常用于Boost的函数,它提供了一个强大的工具,用于存放后续调用的函数和函数对象。Bind 已被即将发布的Library Technical Report所接受。

Bind 库如何改进你的程序?
  • 使函数和函数对象适用于标准库算法
  • 使用一致语法创建绑定器
  • 强大的函数组合

在使用来自于标准库的算法时,你常常需要提供给它们一个函数或一个函数对象。这是对算法的行为进行定制的一个好方法,但你通常需要写一个新的函数对象,因为你没有组合函数或改变参数的顺序等所需的工具。虽然标准库已经提供了一些可用的工具,如 bind1st 和 bind2nd, 但是这不够用。即使功能上够用了,但这通常意味着要忍受笨拙的语法,这些语法通常会让不熟悉这些工具的程序员产生混乱。你需要的是一个解决方案,既具备所需功能,又可以使用普通的语法就地创建函数对象,这正是 Boost.Bind 所要做的。

事实上,泛型绑定器是一种 lambda 表达式,因为通过函数组合,我们可以或多或少在调用点构造一个局部的、无名的函数。在许多情形下这都是需要的,因为它达到了三个目的:减少了代码的数量,使代码更易懂,还有行为的局部化,这意味着更有效的维护。注意,还有另一个 Boost 库,Boost.Lambda, 它具有更多的特性。Boost.Lambda 将在下一章中讨论。为什么你不直接跳到下一个库?因为多数情况下,Boost.Bind 可以完成你要绑定的所有东西,并且学习曲线没那么陡。

Bind 成功的一个关键是采用统一的语法来创建函数对象,以及对于使用该库的类型只有很少的要求。这种设计使得无需关注如何去写与你的类型一起工作的代码,而只需关注我们最关心的一点,代码如何工作以及它实际上做了什么。使用来自标准库的适配器时,如 ptr_fun 和 mem_fun_ref, 代码很容易变得过分冗长,因为我们必须提供这些适配器以便参数可以符合算法的要求。在 Boost.Bind 里不是这样的,它使用了更为精妙的推断系统,并且在自动推断不能适用时提供了一个简单的语法。使用 Bind 的结果就是,你可以写更少的代码,而且代码更易懂。

Bind 如何适用于标准库?

概念上,Bind  是已有的标准库函数 bind1st 和 bind2nd 的泛化,其额外的功能就是允许更为精妙的函数组合。它还减少了对函数指针和类成员指针使用适配器的需要,从而缩短了代码,也减少了出错的机会。Boost.Bind 还包含了对C++标准库的一些常用的扩充,如SGI扩充的 compose1 和 compose2, 还有 select1st 和 select2nd 函数。因此,Bind 非常适用于标准库,而且它也真的非常好用。这些功能被公认为是需要的,最终将被引入到标准库中,也是对STL的扩展。Boost.Bind 已经被即将发布的 Library Technical Report 所接纳。


Bind

头文件: "boost/bind.hpp"

Bind 库创建函数对象来绑定到一个函数(普通函数或成员函数)。不需要直接给出函数的所有参数,参数可以稍后给,这意味着绑定器可以用于创建一个改变了它所绑定到的函数的 arity (参数数量) 的函数对象,或者按照你喜欢的顺序重排参数。

函数 bind 的重载版本的返回类型是未指定的,即不能保证返回的函数对象的特征是怎样的。有时,你需要将对象存于某处,而不是直接把它传送给另一个函数,这时,你要使用 Boost.Function, 后面的部分中讨论。弄明白 bind 函数返回的是什么的关键在于,理解它发生了什么转换。用 bind 函数的一个重载,template<class R, class F> unspecified-1 bind(F f)来作为例子,返回类型就是 (引用自在线文档),"一个函数对象 l ,表达式 l(v1, v2, ..., vm) 等同于 f(),隐式转换为 R"。这样,这个被绑定的函数就被保存在绑定器里面,以后对这个函数对象的调用就会得到被绑定的函数的返回值(如果有),即模板参数 R. 我们在这讨论的实现支持最多九个函数参数。

Bind 的实现包括许多函数和类,但作为用户来说,我们不直接使用除了重载函数 bind 以外的任何东西。所有绑定通过 bind 函数发生,我们可以无须依赖于返回值的类型。使用 bind 时,参数占位符(命名为 _1, _2, 等等)不需要用一个using声明或using指示来引入,因为它们位于匿名名字空间。这样,在使用 Boost.Bind时,没有理由写出以下的代码。

  1. using boost::bind;
  2. using namespace boost;
前面曾经提到过,当前的 Boost.Bind 实现支持九个占位符(_1, _2, _3, 等等),也就是说最多九个参数。粗略地过一下大纲对于深入理解如何进行类型推断是有好处的,还可以知道何时/为何它不总是可以工作的。花点时间分析一下成员函数指针与普通函数的署名特征也是很有用的。你将会看到对于普通函数和类成员函数,各有各的重载版本。还有,对于每一个数量的参数,也都有不同的重载。我不在这里列出所有大纲了,建议你到 www.boost.org参考一下 Boost.Bind 的文档。


用法

Boost.Bind 为函数和函数对象提供了一致的语法,对于值语义和指针语义也一样。我们将从一些简单的例子开始,处理一些简单绑定的用法,然后再转移到通过嵌套绑定进行函数组合。弄明白如何使用 bind 的关键是,占位符的概念。占位符用于表示提供给结果函数对象的参数,Boost.Bind 支持最多九个参数。占位符被命名为 _1, _2, _3, _4, 直至 _9, 你要把它们放在你原先放参数的地方。作为第一个例子,我们定义一个函数,nine_arguments, 它将被一个 bind 表达式调用。

#include <iostream>
#include "boost/bind.hpp"

void nine_arguments(
  int i1,int i2,int i3,int i4,
  int i5,int i6,int i7,int i8, int i9) {
  std::cout << i1 << i2 << i3 << i4 << i5
    << i6 << i7 << i8 << i9 << '\n';
}

int main() {
  int i1=1,i2=2,i3=3,i4=4,i5=5,i6=6,i7=7,i8=8,i9=9;
  (boost::bind(&nine_arguments,_9,_2,_1,_6,_3,_8,_4,_5,_7))
    (i1,i2,i3,i4,i5,i6,i7,i8,i9);
}


在这个例子中,你创建了一个匿名临时绑定器,并立即把参数传递给它的调用操作符来调用它。如你所见,占位符的顺序是被搅乱的,这说明参数的顺序被重新安排了。注意,占位符可以在一个表达式中被多次使用。这个程序的输出如下。

921638457

这表示了占位符对应于它的数字所示位置的参数,即 _1 被第一个参数替换,_2 被第二个参数替换,等等。接下来,你将看到如何调用一个类的成员函数。

调用成员函数

我们来看一下如何用 bind 调用成员函数。我们先来做一些可以用标准库来做的事情,这样可以对比一下用 Boost.Bind 的方法。保存某种类型的元素在一个标准库容器中,一个常见的需要是对某些或全部元素调用一个成员函数。这可以用一个循环来完成,通常也正是这样做的,但还有更好的方法。考虑下面这个简单的类,status, 我们将用它来示范 Boost.Bind 的易用性和强大的功能。

class status {
  std::string name_;
  bool ok_;
public:
  status(const std::string& name):name_(name),ok_(true) {}

  void break_it() {
    ok_=false;
  }

  bool is_broken() const {
    return ok_;
  }

  void report() const {
    std::cout << name_ << " is " <<
      (ok_ ? "working nominally":"terribly broken") << '\n';
  }
};


如果我们把这个类的实例保存在一个 vector, 并且我们需要调用成员函数 report, 我们可能会象下面这样做。

std::vector<status> statuses;
statuses.push_back(status("status 1"));
statuses.push_back(status("status 2"));
statuses.push_back(status("status 3"));
statuses.push_back(status("status 4"));

statuses[1].break_it();
statuses[2].break_it();

for (std::vector<status>::iterator it=statuses.begin();
  it!=statuses.end();++it) {
  it->report();
}


这个循环正确地完成了任务,但它是冗长、低效的(由于要多次调用 statuses.end()),并且不象使用标准库算法 for_each 那样清楚地表明意图。为了用 for_each 来替换这个循环,我们需要用一个适配器来对 vector 元素调用成员函数 report 。这时,由于元素是以值的方式保存的,我们需要的是适配器 mem_fun_ref.

std::for_each(
  statuses.begin(),
  statuses.end(),
  std::mem_fun_ref(&status::report));


这是一个正确、合理的方法,它非常简洁,非常清楚这段代码是干什么的。以下是使用 Boost.Bind 完成相同任务的代码。

std::for_each(
  statuses.begin(),
  statuses.end(),
  boost::bind(&status::report,_1));


这个版本同样的清楚、明白。这是前面所说的占位符的第一个真正的使用,我们同时告诉编译器和代码的读者,_1 用于替换这个函数所调用的绑定器的第一个实际参数。虽然这段代码节省了几个字符,但在这种情况下标准库的 mem_fun_ref 和 bind 之间并没有太大的不同,但是让我们来重用这个例子并把容器改为存储指针。

std::vector<status*> p_statuses;
p_statuses.push_back(new status("status 1"));
p_statuses.push_back(new status("status 2"));
p_statuses.push_back(new status("status 3"));
p_statuses.push_back(new status("status 4"));

p_statuses[1]->break_it();
p_statuses[2]->break_it();


我们还可以使用标准库,但不能再用 mem_fun_ref. 我们需要的是适配器 mem_fun, 它被认为有点用词不当,但它的确正确完成了需要做的工作。

std::for_each(
  p_
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值