bind2nd普通二元函数时无法使用引用类型参数的问题

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/cplusplus-bind2nd-reference/

在使用 STL 的 find_if、count_if 等函数时会发现这些函数使用的参数不是普通的函数指针,而是 functional 函数对象,实际使用函数对象时可以自己定义一个仿函数来实现(实现带参数的 operator () 即可),这个相对比较简单就不写出来了。但有些情况需要直接使用普通的二元函数指针,这时可以使用 ptr_fun 将函数指针转换为函数对象作为 find_if、count_if 等的参数。

先看一个能正常工作的二元函数不使用引用类型参数的代码:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

using namespace std;

class sss
{
public:
	explicit sss(int val) : value(val) {}

	sss(const sss& org)
	{
		cout << "---copy " << &org << " to " << this << endl;
		value = org.value;
	}

	virtual ~sss() {}

	int value;
};

bool find_sss(sss s_chk, int val)
{
	bool ret = (s_chk.value == val);
	s_chk.value = 222;
	return ret;
}

int main(int argc, char ** argv)
{
	vector<sss> vvv;
	vector<sss>::iterator iii;

	vvv.push_back(sss(11));
	vvv.push_back(sss(12));
	vvv.push_back(sss(13));
	vvv.push_back(sss(14));
	vvv.push_back(sss(15));

	cout << "before find_if" << endl;
	iii = find_if(vvv.begin(), vvv.end(), bind2nd(ptr_fun(find_sss), 13));

	if (iii == vvv.end())
		cout << "not found" << endl;
	else
		cout << "index: " << (iii - vvv.begin()) << ", value: " << iii->value << endl;

	return 0;
}

程序很简单,从 vector 中查找符合条件的 sss 对象,find_sss 就是要转换的二元函数指针,第一个参数是 sss 类,通过 ptr_fun 可以使本代码正常工作。

通过下面的运行输出能看出调用 find_sss 时进行了拷贝构造(本程序的编译环境为:Windows 7 32bit, Mingw gcc 3.4.5,Visual Studio 2010中稍有不同,主要在前面的拷贝次数上):

---copy 0x22ff10 to 0x552a58
---copy 0x552a58 to 0x552ad8
---copy 0x22ff10 to 0x552ae0
---copy 0x552ad8 to 0x552af0
---copy 0x552ae0 to 0x552af8
---copy 0x22ff10 to 0x552b00
---copy 0x22ff10 to 0x552b08
---copy 0x552af0 to 0x552b18
---copy 0x552af8 to 0x552b20
---copy 0x552b00 to 0x552b28
---copy 0x552b08 to 0x552b30
---copy 0x22ff10 to 0x552b38
before find_if
---copy 0x552b18 to 0x22fdd0
---copy 0x22fdd0 to 0x22fd40
---copy 0x552b20 to 0x22fdd0
---copy 0x22fdd0 to 0x22fd40
---copy 0x552b28 to 0x22fdd0
---copy 0x22fdd0 to 0x22fd40
index: 2, value: 13

接下来就是实际碰到的问题了,如果将 find_sss 的第一个参数改为 sss 的引用,即第 24 行改为:

bool find_sss(sss& s_chk, int val)

上面的代码就会编译出错(以 Visual Studio 2010 的错误输出为例):


C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xfunctional(341) : error C2535: “bool std::binder2nd<_Fn2>::operator ()(sss &) const”: 已经定义或声明成员函数
with
[
_Fn2=std::pointer_to_binary_function<sss &,int,bool,bool (__cdecl *)(sss &,int)>
]
C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xfunctional(335) : 参见“std::binder2nd<_Fn2>::operator ()”的声明
with
[
_Fn2=std::pointer_to_binary_function<sss &,int,bool,bool (__cdecl *)(sss &,int)>
]
test.cpp(43): 参见对正在编译的类 模板 实例化“std::binder2nd<_Fn2>”的引用
with
[
_Fn2=std::pointer_to_binary_function<sss &,int,bool,bool (__cdecl *)(sss &,int)>
]


网上的码农和攻城师们基本都认为是 STL 本身的问题,传递引用类型参数会造成 reference to reference 问题。

几番尝试之后,发现的解决方法如下:

1、Visual Studio 2010下的不完美解决方法:

将第 43 行改为:

    iii = find_if(vvv.begin(), vvv.end(), bind2nd(pointer_to_binary_function<sss, int, bool, bool(*)(sss&, int)>(find_sss), 13));

这样通过自己给 pointer_to_binary_function 设置模板参数避免编译出错,实际运行中会发现 find_if 查找可以正常工作了,但第 27 行中修改引用类的值会没有效果,因为这种方式不是真正的引用,仍然有拷贝构造,由于需要用到这些二元函数的场合一般不需要修改数据,使用起来没有太大问题。

2、gcc下的不完美解决方法:

如果 gcc 下用上面的改动,你会发现由于 gcc 下 pointer_to_binary_function 只有 3 个参数,无法顺利修改编译,但可以这样折中,将第 43 行改为:

    iii = find_if(vvv.begin(), vvv.end(), bind2nd(ptr_fun((bool(*)(sss, int)) find_sss), 13));

通过强制类型转换来实现,稍显恶心,查找可以正常工作,但依然是拷贝构造。需要注意此方法如果在 Visual Studio 中使用会出错。

3、终极解决方案- 使用 boost 库:

首先头文件中增加:

#include <boost/functional.hpp>

然后原来的第 43 行改为:

    iii = find_if(vvv.begin(), vvv.end(), boost::bind2nd(boost::ptr_fun(find_sss), 13));

编译运行之后发现 find_if 查找可以正常工作,而且现在是真正的引用,只能说 boost 的 functional 相比 STL 的实在是好强大,哈哈。

运行输出如下(使用 Mingw gcc 3.4.5 编译),find_if 找到的值已经被 find_sss 修改:

---copy 0x22ff10 to 0x7f2a58
---copy 0x7f2a58 to 0x7f2ad8
---copy 0x22ff10 to 0x7f2ae0
---copy 0x7f2ad8 to 0x7f2af0
---copy 0x7f2ae0 to 0x7f2af8
---copy 0x22ff10 to 0x7f2b00
---copy 0x22ff10 to 0x7f2b08
---copy 0x7f2af0 to 0x7f2b18
---copy 0x7f2af8 to 0x7f2b20
---copy 0x7f2b00 to 0x7f2b28
---copy 0x7f2b08 to 0x7f2b30
---copy 0x22ff10 to 0x7f2b38
before find_if
index: 2, value: 222

以上仅为个人小心得,有任何问题欢迎指正,玩的开心咯 ^_^

bind2nd普通二元函数时无法使用引用类型参数的问题》上的评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*