C++11特性:可调用对象绑定器的使用

2023-12-20 05:35:45

std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。通俗来讲,它主要有两大作用:

1. 将可调用对象与其参数一起绑定成一个仿函数。
2. 将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数。

绑定器函数使用语法格式如下:

// 绑定非类成员函数/变量
auto f = std::bind(可调用对象地址, 绑定的参数/占位符);
// 绑定类成员函/变量
auto f = std::bind(类函数/成员地址, 类实例对象地址, 绑定的参数/占位符);

这就解决了function不能包装类成员的弊端。?

?接下来来解释什么是占位符:

placeholders::_1是一个占位符,代表这个位置将在函数调用时被传入的第一个参数所替代。同样还有其他的占位符placeholders::_2、placeholders::_3、placeholders::_4、placeholders::_5等……

有了占位符的概念之后,使得std::bind的使用变得非常灵活:

#include <iostream>
#include <functional>
using namespace std;

void output(int x, int y)
{
    cout << x << " " << y << endl;
}

int main(void)
{
    // 使用绑定器绑定可调用对象和参数, 并调用得到的仿函数
    bind(output, 1, 2)();
    bind(output, placeholders::_1, 2)(10);
    bind(output, 2, placeholders::_1)(10);

    // error, 调用时没有第二个参数
    // bind(output, 2, placeholders::_2)(10);
    // 调用时第一个参数10被吞掉了,没有被使用
    bind(output, 2, placeholders::_2)(10, 20);

    bind(output, placeholders::_1, placeholders::_2)(10, 20);
    bind(output, placeholders::_2, placeholders::_1)(10, 20);


    return 0;
}

示例代码执行的结果:

1  2		// bind(output, 1, 2)();
10 2		// bind(output, placeholders::_1, 2)(10);
2 10		// bind(output, 2, placeholders::_1)(10);
2 20		// bind(output, 2, placeholders::_2)(10, 20);
10 20		// bind(output, placeholders::_1, placeholders::_2)(10, 20);
20 10		// bind(output, placeholders::_2, placeholders::_1)(10, 20);

?第几个参数也就是参数列表中的传入顺序。

下面来看一个关于绑定器的实际使用的例子:

#include <iostream>
#include <functional>
using namespace std;

void callFunc(int x, const function<void(int)>& f)//第三个参数是包装器类型
{
    //传入的参数必须是一个可调用对象
    //通过绑定器就可以得到可调用对象了
    if (x % 2 == 0)
    {
        f(x);
    }
}

void output(int x)
{
    cout << x << " ";
}

void output_add(int x)
{
    cout << x + 10 << " ";
}

int main(void)
{
    // 使用绑定器绑定可调用对象和参数
    //这里指定的是占位符,所以callFunc()里面的参数生效了
    //如果占位符这里写上一个实参,那么callFunc()里面传的参也不会生效
    auto f1 = bind(output, placeholders::_1);
    for (int i = 0; i < 10; ++i)
    {
        callFunc(i, f1);
    }
    cout << endl;

    auto f2 = bind(output_add, placeholders::_1);
    for (int i = 0; i < 10; ++i)
    {
        callFunc(i, f2);
    }
    cout << endl;

    return 0;
}

f1和f2是两个仿函数。

若你想要直接进行调用,就直接在bind函数后面加上()传递相应的参数就可以了,这时候f就不需要了。但是如果你想要进行包装,就得用到f。?

测试代码输出的结果:

0 2 4 6 8
10 12 14 16 18

在上面的程序中,使用了std::bind绑定器,在函数外部通过绑定不同的函数,控制了最后执行的结果。std::bind绑定器返回的是一个仿函数类型,得到的返回值可以直接赋值给一个std::function,在使用的时候我们并不需要关心绑定器的返回值类型,使用auto进行自动类型推导就可以了。

?

对于上述代码中占位符的进一步理解,我添加了对比结果输出的代码:

#include <iostream>
#include <functional>
using namespace std;

void callFunc(int x, const function<void(int)>& f)//第三个参数是包装器类型
{
	//传入的参数必须是一个可调用对象
	//通过绑定器就可以得到可调用对象了
	if (x % 2 == 0)
	{
		f(x);
	}
}

void output(int x)
{
	cout << x << " ";
}

void output_add(int x)
{
	cout << x + 10 << " ";
}

int main(void)
{
	// 使用绑定器绑定可调用对象和参数
	//这里指定的是占位符,所以callFunc()里面的参数生效了
	//如果占位符这里写上一个实参,那么callFunc()里面传的参也不会生效
	auto f1 = bind(output, placeholders::_1);
	for (int i = 0; i < 10; ++i)
	{
		callFunc(i, f1);
	}
	cout << endl;

	auto f2 = bind(output_add, placeholders::_1);
	for (int i = 0; i < 10; ++i)
	{
		callFunc(i, f2);
	}
	cout << endl;
	cout << "--------------------" << endl;
	auto f11 = bind(output, 100);
	for (int i = 0; i < 10; ++i)
	{
		callFunc(i, f11);
	}
	cout << endl;

	auto f22 = bind(output_add, 200);
	for (int i = 0; i < 10; ++i)
	{
		callFunc(i, f22);
	}
	cout << endl;

	return 0;
}

运行结果为:

?

由此输出结果可以看出若bind绑定中已经有了固定的实参,后续再传参实参列表中的参数是无效的。只有在绑定的时候指定占位符,才会生效。

?

可调用对象包装器std::function是不能实现对类成员函数指针或者类成员指针的包装的,但是通过绑定器std::bind的配合之后,就可以完美的解决这个问题了,再来看一个例子,然后再解释里边的细节:

#include <iostream>
#include <functional>
using namespace std;

class Test
{
public:
	void output(int x, int y)
	{
		cout << "x: " << x << ", y: " << y << endl;
	}
	int m_number = 100;
};

int main(void)
{
	//因为是类的成员函数属于对象,所以要先创建对象
	Test t;
	// 绑定类成员函数
	function<void(int, int)> f1 =
		bind(&Test::output, &t, placeholders::_1, placeholders::_2);
	// 绑定类成员变量(公共)
	function<int& (void)> f2 = bind(&Test::m_number, &t);

	// 调用
	f1(520, 1314);
	f2() = 2333;
	cout << "t.m_number: " << t.m_number << endl;

	return 0;
}

上述示例代码的运行结果为:

?

通过绑定类的成员函数和成员变量都会得到得到一个仿函数,这个仿函数就是一个可调用对象,使用包装器可以包装这个可调用对象中的仿函数。

注意对于成员变量,包装出来的变量,也是可读可写的,所以为引用类型,当然也可以使用auto自动推导类型。

需要注意的点:

但是使用auto自动推导和显示指定的类型是不相等的,因为绑定器绑定出来是一个仿函数,所以auto推导出来是仿函数,而显示指定得到的就是把仿函数进行包装的包装器类型,他们不是等价的,只不过是显示指定的做了一个隐式类型转换。?

占位符的编号是相对于原始函数的形参位置而言的,而不是相对于 std::bind 创建的函数对象(fn3)的形参位置。

例如:

// 普通函数
void show(int bh, const string& message) {
	cout << "亲爱的" << bh << "号," << message << endl;
}


//参数调换位置
function<void(const string&, int)> fn3 = bind(show, placeholders::_2, placeholders::_1);
fn3("我是一只傻傻鸟。", 1);

这里输出的还是:亲爱的1号,我是一只傻傻鸟。?

本文参考:可调用对象包装器、绑定器 | 爱编程的大丙 (subingwen.cn)

?

?

文章来源:https://blog.csdn.net/qq_73185160/article/details/135085062
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。