std::function与可调用实体
C++中的可调用实体
在C++中,可调用实体(Callable Entity)是指可以像函数一样被调用的对象。主要包括以下几类:
- 函数指针、普通函数
- 函数对象(仿函数)
- lambda表达式
- 静态成员函数、非静态成员函数
int add(int a = 1, int b = 2) {
return a + b;
}
int main() {
add();
return 0;
}struct Add {
int operator()(int a = 1, int b = 2) {
return a + b;
}
} add;
int main() {
add();
return 0;
}int main() {
auto add = [](int a = 1, int b = 2) -> int {
return a + b;
}
add();
return 0;
}class Add {
public:
int add1(int a = 1, int b = 2) {
return a + b;
}
static int add2(int a = 1, int b = 2){
}
}
int main() {
Add a;
a.add1();
Add::add2();
}上述的例子中,可调用实体和调用函数的形式是一样的(都是用()调用)。
本质上讲,可调用实体在编译时他们的执行逻辑写入了代码段(.text段)。而C++作为一个强类型的语言,不同的可调用实体都被视为是不一样的类型。
std::function
C++11提供了一种可调用实体包装器std::function,他是一个类模板,可以容纳所有的可调用实体。
#include <functional>
int add1(int a, int b) { return a + b; }
auto add2 = [](int a, int b) { return a + b; };
struct Add1 {
int operator()(int a, int b) { return a + b; }
} add3;
class Add2 {
public:
static int add4(int a, int b) { return a + b; }
int add5(int a, int b) { return a + b; }
};
int main() {
std::function<int(int, int)> func1 = add1;
std::function<int(int, int)> func2 = add2;
std::function<int(int, int)> func3 = add3;
std::function<int(int, int)> func4 = &Add2::add4;
Add2 a;
// 成员函数隐含this指针,必须使用std::bind处理
std::function<int(int, int)> func5 =
std::bind(&Add2::add5, &a, std::placeholders::_1, std::placeholders::_2);
return 0;
}将可调用实体赋给std::function时,注意,是否需要使用&符号来取地址,每个可调用实体的表现是不一样的。
- 普通函数:为了兼容C,现代C++可以认为函数名和对函数进行取地址是等价的,函数名可以认为就是函数的入口地址。
- lambda表达式:本质上是一个对象,而
std::function可以直接接收可调用实例对象,不需要&。 - 仿函数:仿函数是一个类,类中重载了
()操作符,所以仿函数名本质是类名,而不是函数名。所以实例对象不需要&。 - 静态成员函数:需要使用
&取函数入口地址。 - 非静态成员函数:需要使用
std::bind显式传入对象的地址
std::bind
C++11标准库提供了一个std::bind函数,可以用来将可调用实体和参数绑定在一起,生成一个新的可调用实体。
std::placeholders占位符
参数可以使用std::placeholders占位符,表示在调用时再指定具体的值。也可以直接给定具体的参数,类似于绑定了默认值。
可以说std::bind提供了一种改变可调用实体参数位置以及默认值的方式。
#include <iostream>
void show(int a, int b) {
std::cout << a << " " << b << std::endl;
}
int main() {
auto func = std::bind(show, std::placeholders::_1, std::placeholders::_2);
func(1, 2);
return 0;
}通常的使用形式:std::bind(<可调用实体>, <参数1, 参数2, ...>),这里第一个参数是可调用实体,后面的参数是可调用实体的参数。
后面的参数与可调用实体的参数是一一对应的关系。即传给std::bind的参数,会按顺序传给可调用实体。
占位符的作用:绑定调用新的可调用对象时对应位置的参数。
#include <iostream>
void show(int a, int b, int c) {
std::cout << a << " " << b << " " << c << std::endl;
}
int main() {
auto func1 = std::bind(show, 1, 2, 3);
// 1,2和3会按顺序传给show,所以此处可以不写参数
// 需要注意的是,即便写参数也无效,还是会传入std::bind绑定的值。
func1();
// 输出:1 2 3
// 占位符的作用:不传入默认值,指定在调用新的可调用对象时再传入。
auto func2 = std::bind(show, std::placeholders::_1, std::placeholders::_2, 3);
// 这里的1就传给了_1, 2传给了_2
func2(1, 2);
// 输出:1 2 3
auto func3 = std::bind(show, std::placeholders::_1, 2, std::placeholders::_2)
// 这里的1传给了_1, 2传给了2,3没有意义,甚至可以不写,
// func3需要多少个参数取决于占位符的最大值
func3(1, 2, 3);
// 输出:1 2 2
return 0;
}再重新梳理以下占位符的意义:
对于show函数而言,其本身被调用必然需要传入三个参数,这点是毋庸置疑的。而std::bind以及std::placeholders的作用不过是改变参数顺序或者指定默认值而已。
auto func = std::bind(show, <1>, <2>, <3>)这里的<1>, <2>, <3>会作为参数会按顺序传给show函数。
如果在<1>, <2>, <3>中使用了std::placeholders,他指代的是func上对应位置的参数。
例如:func(3, 4, 5),那么std::placeholders::_1指代的就是3,std::placeholders::_2指代的就是4,std::placeholders::_3指代的就是5。
所以,std::bind返回的可调用对象func中的参数个数,取决于std::bind占位符所使用的最大值,因为占位符决定了在func的参数列表的对应位置的参数需要被使用,而其他未提及的参数则没有意义。
非静态成员函数使用std::bind
非静态成员函数与实例对象是绑定的,并且非静态成员实际上在第一个参数位置上隐含this指针。所以,使用std::bind绑定非静态成员函数时,需要显式传入实例对象的地址。
#include <iostream>
struct A {
void show(int a, int b) { std::cout << a << " " << b << std::endl; }
}
int main() {
A a;
// &a 传入实例对象的地址
auto func = std::bind(&A::show, &a, std::placeholders::_1, std::placeholders::_2);
func(1, 2);
return 0;
}使用场景
在形参中使用std::function作为可调用实体的包装器,这样就可以将可调用实体作为参数传递给函数。这种场景在回调机制中经常被用到。
