在上一篇文章中,我们提到可调用对象(callable object),其中一种就是std::bind表达式。在这篇文章中,我们来谈谈std::bind表达式。
关于std::bind的定义如下:
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
template< class R, class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
关于其解释可以参看:http://en.cppreference.com/w/cpp/utility/functional/bind
看上去不好理解,咱们还是以一个简单的例子来进行说明,毕竟一个例子胜过千言万语。比如有如下函数实现两个整数相加:
int add(int x, int y) {
return x + y;
}
假如现在我们要实现某个整数加上6的函数功能,除了写一个类似的函数:
int add1(int x) {
return x + 6;
}
之外,我们还可以复用上面的add函数:
auto add1 = bind(add, placeholders::_1, 6);
cout << add1(2) << endl; // print 8
简单说,bind就是一个函数包装器(function wrapper),在一个通用化函数的基础上,固定一个或多个输入参数,包装成一个更加简化的函数。其好处有:
- 代码复用。上面的例子过于简单,可能没有表现出这个好处。但我们可以想象一下加入上面的add函数实现了很复杂的逻辑,通过copy代码的方式实现类似功能,极其容易引入bug。
- 易于维护,这其实也是代码复用带来的好处,代码逻辑写在一处比分散在多处更容易维护。
如果说这两点好处还不足以说服我们使用std::bind,那接下来我们要探讨的用法才是std::bind的最大用途。
在上一篇文章中,我们曾提过对象的成员函数无法与函数指针相容,主要原因在于类的成员函数都包含有一个隐含的this参数。比如:
class Simple
{
private:
int m_id;
public:
Simple(int id)
{
setID(id);
}
void setID(int id) { m_id = id; }
int getID() { return m_id; }
};
其中的setID成员函数经过编译器的处理,等价于:
void setID(Simple* const this, int id) { this->m_id = id; }
调用代码
simple.setID(2);
经过编译器处理,成为:
setID(&simple, 2);
这一切都是编译器在处理,对于实现者和调用者并不需要关心这些细节。回想之前的std::bind,我们是否可以将指针绑定到成员函数而包装成另一个函数呢?
#include <functional>
#include <iostream>
struct Foo {
int value;
void f() { std::cout << "f(" << this->value << ")\n"; }
void g() { std::cout << "g(" << this->value << ")\n"; }
};
void apply(std::function<void()> func) {
func();
}
int main() {
Foo foo1{1};
Foo foo2{2};
apply(std::bind(&Foo::f, &foo1));
apply(std::bind(&Foo::g, &foo2));
}
在上述代码中,我们将Foo的成员函数包装成了
std::function<void()>
这样的类型,从而可以用在回调等场景。
当然,在实际项目的代码中,还有很多bind的用途,比如chromium项目中就有大量的bind和callback,虽然里面并不是使用的std::bind,而是使用自己定义的base::bind,但在原理上是差不多的。