使用BOOST BIND库拔高C++程序性能-基础及绑定成员函数

使用BOOST BIND库提高C++程序性能--基础及绑定成员函数

英文原文:How the Boost Bind Library Can Improve Your C++ Programs

  • By Björn Karlsson
  • Aug 26, 2005

翻译:Boost.Bind的用法

Boost.Bind为函数和函数对象,值语义和指针提供语义了一致的语法。我们首先通过一些简单的例子来看看它的基本用法,之后我们会延伸到嵌套绑定以实现功能组合。理解bind用法的一个关键是理解占位符(placeholder)的概念。占位符表示该参数将在函数对象里面提供。Boost.Bind提供多达9个这样的参数——_1, _2, _3, _4, _5,_6,_7,_8, _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);
}

在这个程序中,你可以创建临时的匿名绑定器并且立即传参调用。如你所见,占位符的顺序是在本例中是混乱的,这使得参数的顺序也被打乱。另外,占位符可以在表达式中重复使用。示例程序1的输出是:

921638457

占位符的序数与参数的位置是对应的,也就是说,_1被换为第一个参数,_2被换为第二个参数,以此类推。


原创:编程实验

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

void PlaceholderTest(int a, int b, int c)
{
	std::cout << a << b << c << std::endl;
}

int main()
{
	int a = 1, b = 2, c = 3;
	//(boost::bind(&PlaceholderTest, _1, _2))(a, b);	//error
	//(boost::bind(&PlaceholderTest, _1, _2, _1, _3))(a, b, c);	//error
	//(boost::bind(&PlaceholderTest, _1, _2, _3))(a, b);	//error
	(boost::bind(&PlaceholderTest, _1, _2, _1))(a, b);	//OK,output:121
	(boost::bind(&PlaceholderTest, 99, _2, _1))(a, b)	//OK,output:9921
} 

总结:

1、参数占位符数量与函数形参数量必须一致

2、占位符的替代值可以少于占位符数量

3、占位符和实参可以混合使用


翻译:调用成员函数(1)

我们看看如何使用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();
}

for循环能够正确完成操作,但是它冗长、低效(每次都要检查statuses.end()),还不如使用标准库中专为这种操作设计的for_each算法来的清楚。为了使用for_each替代for循环,我们需要为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));

bind版本仍然清晰明了。这是我们第一次真正使用上面提及的Bind库占位符,它向编译器和代码阅读者传递了这样一个信息,_1将在调用绑定器的函数中被实参替换。尽管这段代码长度减少了,但在本例中,它与使用标准库mem_fun_ref几乎没有差别。


原创:编程实验

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

#include <boost/bind.hpp>

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

	void BreakIt()
	{
		ok_ = false;
	}

	bool IsBroken() const
	{
		return ok_;
	}

	void Report() const
	{
		std::cout << name_ << " is " << (ok_ ? "ok" : "broken") << std::endl;
	}

private:
	std::string name_;
	bool ok_;
};

int main()
{
	std::vector<Status> v_status;

	
	v_status.push_back(Status("status 1"));
	v_status.push_back(Status("status 2"));
	v_status.push_back(Status("status 3"));
	v_status.push_back(Status("status 4"));

	v_status[1].BreakIt();
	v_status[2].BreakIt();

	std::cout << "use \"for\":" << std::endl;
	for (std::vector<Status>::iterator it = v_status.begin(); it < v_status.end(); it++)
	{
		it->Report();
	}

	std::cout << "\nuse \"for_each, mem_fun_ref\":" << std::endl;
	std::for_each(v_status.begin(), v_status.end(), std::mem_fun_ref(&Status::Report));

	std::cout << "\nuse \"for_each, bind\":" << std::endl;
	//std::for_each(v_status.begin(), v_status.end(), boost::bind(&Status::Report));	//error
	std::for_each(v_status.begin(), v_status.end(), boost::bind(&Status::Report, _1));
}

总结:

bind成员函数最大的不同是,必须指明调用该函数的实例对象,代码中用“_1”表示。

翻译:调用成员函数(2)

下面,我们稍微改造一下vector容器,让它装入指针而不是值:

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_statuses.begin(),
	p_statuses.end(),
	std::mem_fun(&status::report));

注意到,这段代码的语法已经有所改变,尽管我们要做的工作几乎相同。当然,如果代码的语法和上面的例子一样就最好了,这样我们就可以更多地关注代码到底做了些什么而不是它怎么做的。使用Bind,我们不需要显式指明要处理的元素是指针(这在容器类型中已经说明了,重复的信息在现代的库里面显然是不必要的)

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

如你所见,这与之前非指针元素的代码没有任何区别。也就是说如果你理解了刚才的bind,那么这个也能理解。


原创:编程实验

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

#include <boost/bind.hpp>

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

	void BreakIt()
	{
		ok_ = false;
	}

	bool IsBroken() const
	{
		return ok_;
	}

	void Report() const
	{
		std::cout << name_ << " is " << (ok_ ? "ok" : "broken") << std::endl;
	}

private:
	std::string name_;
	bool ok_;
};

int main()
{
	std::vector<Status*> v_pstatus;
	
	v_pstatus.push_back(new Status("status 1"));
	v_pstatus.push_back(new Status("status 2"));
	v_pstatus.push_back(new Status("status 3"));
	v_pstatus.push_back(new Status("status 4"));

	v_pstatus[1]->BreakIt();
	v_pstatus[2]->BreakIt();

	std::cout << "use \"for\":" << std::endl;
	for (std::vector<Status*>::iterator it = v_pstatus.begin(); it < v_pstatus.end(); it++)
	{
		(*it)->Report();
	}

	std::cout << "\nuse \"for_each, mem_fun_ref\":" << std::endl;
	std::for_each(v_pstatus.begin(), v_pstatus.end(), std::mem_fun(&Status::Report));

	std::cout << "\nuse \"for_each, bind\":" << std::endl;

	std::for_each(v_pstatus.begin(), v_pstatus.end(), boost::bind(&Status::Report, _1));
}

总结:

只有bind保持了形式不变


翻译:调用成员函数(3)

我们决定使用指针后,有另外一个问题,即指针的生命周期控制。我们必须手动释放p_statuses中的元素,这很容易出错而且没有必要。因此,我们可能选择使用智能指针(smart pointers),代码变化如下:

std::vector<boost::shared_ptr<status> > s_statuses;
s_statuses.push_back(
	boost::shared_ptr<status>(new status("status 1")));
s_statuses.push_back(
	boost::shared_ptr<status>(new status("status 2")));
s_statuses.push_back(
	boost::shared_ptr<status>(new status("status 3")));
s_statuses.push_back(
	boost::shared_ptr<status>(new status("status 4")));
s_statuses[1]->break_it();
s_statuses[2]->break_it();

现在,我们该使用标准库中的哪个适配器了?由于智能指针并没有report成员函数,mem_fun和mem_fun_ref都不能用了。如下代码会编译失败。

std::for_each(
	s_statuses.begin(),
	s_statuses.end(),
	std::mem_fun(&status::report));

我们的好运用完了,标准库并不能帮我们完成这个任务。因此,我们只能借助于之前想避开的for形式或者……Boost.Bind,它可以完全正确地完成任务。

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

这与前面的代码是完全一样的(除了容器的名字)。同样的语法可以用于绑定值语义、指针语义或者只能指针。有时候,不同的语法可以帮助我们理解代码,但在我们讨论的情况中不是这样——我们手中的任务是在容器的元素上调用成员函数,没有其他的需求。语法一致的价值是不容轻视的,它既帮助了写代码的人,也帮助了以后维护代码的人。(当然,实际上我们没有写需要维护的代码,但出于参数的考虑,让我们假装是这样做的吧^_^)。


原创:编程实验

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

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

	void BreakIt()
	{
		ok_ = false;
	}

	bool IsBroken() const
	{
		return ok_;
	}

	void Report() const
	{
		std::cout << name_ << " is " << (ok_ ? "ok" : "broken") << std::endl;
	}

private:
	std::string name_;
	bool ok_;
};

int main()
{
	std::vector< boost::shared_ptr<Status> > v_spstatus;
	
	v_spstatus.push_back(boost::shared_ptr<Status>(new Status("status 1")));
	v_spstatus.push_back(boost::shared_ptr<Status>(new Status("status 2")));
	v_spstatus.push_back(boost::shared_ptr<Status>(new Status("status 3")));
	v_spstatus.push_back(boost::shared_ptr<Status>(new Status("status 4")));

	v_spstatus[1]->BreakIt();
	v_spstatus[2]->BreakIt();

	//std::cout << "use \"for\":" << std::endl;
	//for (std::vector<Status*>::iterator it = v_spstatus.begin(); it < v_spstatus.end(); it++)
	//{
	//	(*it)->Report();
	//}

	//std::cout << "\nuse \"for_each, mem_fun_ref\":" << std::endl;
	//std::for_each(v_spstatus.begin(), v_spstatus.end(), std::mem_fun(&Status::Report));

	std::cout << "\nuse \"for_each, bind\":" << std::endl;
	std::for_each(v_spstatus.begin(), v_spstatus.end(), boost::bind(&Status::Report, _1));
}

总结:

1、标准库中的方法不再能够使用

2、使用share_ptr必须引入头文件#include <boost/shared_ptr.hpp>


这些例子展示了一些Boost.Bind最基本最常用的情况,也是它最擅长的方面。尽管标准库也提供了一些基本工具让我们完成同样的任务,但我们看到Bind提供了语法一致性和一些标准库目前没有的扩展功能。