Boost.bind和占位符的实现原理


#1

boost.bind是个非常强大的工具,有了它,我们可以轻松的将不同模块的代码,从物理上分离,从而达到解耦的作用。

当你开始尝试使用boost.bind来进行编程的时候,你会发现自己不知不觉中贱贱的爱上了boost.bind,因为作用不止是解耦,还有很多说不尽道不明的地方需要它。

但是从来很少有人详细的说明它是如何工作(实现)的,这里,我打算用一小段简单的代码,模拟boost.bind以及占位符的实现,以达到阐述boost.bind原理,希望能对你有所帮助。

namespace
{
	class placeholder_ {};
	placeholder_ __1; // 以双下划线开头的占位符__1,双下划线区别下boost.bind里的_1。
}

// 一个接受单个参数的用于bind类的成员函数模板实现,实例化后的对象,实际上就是一个函数对象。
// 这个函数对象的调用没有参数(即operator()),因为参数在构造时传入,并保存在成员Arg& a_中,
// 这样,在调用这个函数对象时,就不再需要传入参数了。
template <typename R, typename T, typename Arg>
class simple_bind_t
{
private:
	typedef R (T::*F)(Arg);
	F f_;
	T* t_;
	Arg& a_;

public:
	// 构造函数,确定了F,T,Arg,分别是成员函数指针,类对象指针,实际传入的参数。
	// 比如在: simple_bind(&bind_test::print_string, &t, h)(); 中,第一个就是类的成员函数地址。
	// 第二个就是指向对象的地址,第三个就是实际调用的参数h。
	simple_bind_t(F f, T* t, Arg &a)
		: f_(f), t_(t), a_(a)
	{}

	R operator()() // 无参数,参数在bind时确实,并保存在a_,调用时就不需要传入参数了。
	{
		return (t_->*f_)(a_);
	}
};

// 这个类基本同上,也是一个接受单个参数的用于bind类的成员函数模板实现,唯一不同的是
// 它主要是为了使用占位符,所以,它的构造函数参数没有了Arg,Arg是在这个函数对象真正
// 被调用时,由用户传入。
template <typename R, typename T, typename Arg>
class simple_bind_t2
{
private:
	typedef R (T::*F)(Arg);
	F f_;
	T* t_;

public:
	simple_bind_t2(F f, T* t)
		: f_(f), t_(t)
	{}

	R operator()(Arg& a) // 有参数! 调用时由用户传入!
	{
		return (t_->*f_)(a);
	}
};

// 下面是各种simple_bind的重载,这样就可以在用户使用的时候,传入不同的参数,自动
// 匹配到相应的simple_bind函数。

// 第一个simple_bind函数实现,调用参数由用户传入,并保存到simple_bind_t的成员a_里面。
// 这个函数返回simple_bind_t实例化后的函数对象,调用这个函数对象时无需传入参数。
template <typename R, typename T, typename Arg>
simple_bind_t<R, T, Arg> simple_bind(R (T::*f)(Arg), T* t, Arg& a)
{
	return simple_bind_t<R, T, Arg>(f, t, a);
}

// 第二个simple_bind函数实现,可以看到占位符对象实际上是被丢弃掉
// 的,调用参数在实际调用时传入。
template <typename R, typename T, typename Arg>
simple_bind_t2<R, T, Arg> simple_bind(R (T::*f)(Arg), T* t, placeholder_& a)
{
	return simple_bind_t2<R, T, Arg>(f, t);
}


// 一个用于我们自己实现的simple_bind的类。
class bind_test
{
public:
	void print_string(const std::string str)
	{
		printf("%s", str.c_str());
	}
};

// 下面是测试代码。
void test()
{
	bind_test t;
	std::string h = "hehe\n";

	// 不使用占位符,直接在bind时传入,调用时不需要参数。
	simple_bind(&bind_test::print_string, &t, h)();

	// 使用占位符,参数在调用时传入。
	simple_bind(&bind_test::print_string, &t, __1)(h);

	// 还可以使用boost.function保存我们自己实现的simple_bind绑定后的结果,和
	// 使用boost.function保存boost.bind完全兼容。
	// 由此,大家可以想象一下boost.function的实现原理。
	boost::function<void (const std::string)> f;
	f = simple_bind(&bind_test::print_string, &t, __1);
	f(h);
}

结论:

bind返回的结果实际是一个函数对象(注意:和boost.function没有关系,有人认为bind返回的是boost.function,这是错误的!bind返回的东西和boost.function没有关联。),参数和函数指针被保存在这个函数对象里面,在函数对象的operator()中实际调用。

OK,到这里为止,如果你仔细阅读了上面的代码以及注解,并理解了bind的话,请点击赞表示支持!谢谢! 如果有什么不明白,欢迎回复讨论。


闭包 探秘
[系列教程] 通往现代c++之路之1 --- 你要摒弃的几个 c++ 陋习
#2

不错, jack 应该做上 我那个 闭包的链接, 这样用户浏览到相关主题, 都能继续扩展学习, 而不是每个主题都孤立起来, 对吧?


#3

http://avboost.com/t/topic/225http://avboost.com/t/topic/226 看看 bind 的用处


#4

我来化蛇添足下,看了楼主的例子后,写了个可以绑定两个参数的bind, 挺麻烦的,感觉boost的实现应该有更好的方法,否则得写多少重载啊! 看看我的思路是否正确:

#include <iostream>
#include <functional>

namespace{
class placeholder_ {};
placeholder_ __1; // 以双下划线开头的占位符__1,双下划线区别下boost.bind里的_1。
placeholder_ __2;
}

template <typename R, typename Arg1, typename Arg2>
class simple_binder2_t{
public:
    simple_binder2_t(R(*f)(Arg1, Arg2)) : m_f(f){}
    R operator()(Arg1 arg1, Arg2 arg2){
	    return m_f(arg1, arg2);
    }

private:
    R(*m_f)(Arg1, Arg2);
};

template <typename R, typename Arg1, typename Arg2>
class simple_binder2_1st_t{
public:
    simple_binder2_1st_t(R(*f)(Arg1, Arg2), Arg1 arg1) : m_f(f), m_arg1(arg1){}
    R operator()(Arg1 arg2){
    	return m_f(m_arg1, arg2);
    }

private:
	R(*m_f)(Arg1, Arg2);
	Arg1 m_arg1;
};

template <typename R, typename Arg1, typename Arg2>
class simple_binder2_2st_t{
public:
    simple_binder2_2st_t(R(*f)(Arg1, Arg2), Arg2 arg2) : m_f(f), m_arg2(arg2){}
    R operator()(Arg1 arg1){
    	return m_f(arg1, m_arg2);
    }

private:
    R(*m_f)(Arg1, Arg2);
    Arg2 m_arg2;
};

template <typename R, typename Arg1, typename Arg2>
simple_binder2_t<R, Arg1, Arg2> simple_bind(R(*f)(Arg1, Arg2), placeholder_ &a, placeholder_ &b){
	return simple_binder2_t<R, Arg1, Arg2>(f);
}

template <typename R, typename Arg1, typename Arg2>
simple_binder2_1st_t<R, Arg1, Arg2> simple_bind(R(*f)(Arg1, Arg2) , Arg1 a, placeholder_ &b){
    return simple_binder2_1st_t<R, Arg1, Arg2>(f, a);
}

template <typename R, typename Arg1, typename Arg2>
simple_binder2_2st_t<R, Arg1, Arg2> simple_bind(R(*f)(Arg1, Arg2), placeholder_ &a, Arg2 b){
    return simple_binder2_2st_t<R, Arg1, Arg2>(f, b);
}

int test2(int a, int b){
	return a + b;
}

int main(){
    std::cout << simple_bind<int>(test2, 13, __2)(2) << std::endl;
    std::cout << simple_bind<int>(test2, __1, 13)(2) << std::endl;

    std::function<int(int)> f;
    f = simple_bind(test2, 13, __2);
    std::cout << f(2) << std::endl;
}

#5

我看bind的文档有个例子,就是用function来保存bind的返回结果的呀。


#6

确实写了很多重载, 不过 Boost.bind 的那么多重载可不是手写的, 而是一段脚本自动生成的, 所以再多的重载也无所谓了吧.


#7

function是一个概念,表示符合这种接口的“函数”,它可以保存符合这个概念的任何东西 例如function<int(int)> 表示 具有一个int类型的参数,返回值为int的函数或函数对象。 bind的返回值是一个函数对象


#8

奥,不明觉厉~让我凑齐14个字。


#9

function 的实现可是把全部的 C++ 特性都用上了, 重载 模板 虚函数 … 全用上了. 堪称最复杂的库.


#10

function 可以保存 bind 的返回结果, 不代表 bind 的返回结果是 function


#11

昨天在群里问了,也测试了下,bind能够配合private修饰class成员函数使用

搭配std::for_each用起来感觉也停舒服(注意引用变量使用boost::ref)

但是对于有默认参数的函数,比如这样int add(int a, int b = 1, int c = 2);这种,使用std::for_each配合bind的时候,这样写std::for_each(vec.begin(), vec.end(), boost::bind(&AClass::add, &AObj, _1, 1, 2) )ok,那我有没有办法也使用默认的参数,只传一个参数a进去呢?


#12

bind 不能使用默认参数.

死了这条心吧


#13

稍微看了一下function,果然看不懂


#14

待阅读,待评论,待整理


#15

可以参考一个 短小精悍、优雅的bind的实现 https://github.com/cplusplus-study/fork_stl/blob/master/include/bind.hpp


#16

哇, 山寨 STL 啊