一、可调用对象
介绍两个概念:调用运算符和可调用对象
-
调用运算符
调用运算符,即:() 。跟随在函数名之后的一对括号 “()”,起到调用函数的效果,传递给函数的参数放置在括号内。
-
可调用对象
对于一个对象或者一个表达式,如果可以对其使用调用运算符,则这个对象或者表达式可被称为可调用对象。所以可调用对象看起来就是可以像函数一样调用的对象。
分类
可调用对象可以分为如下几类:
- 普通函数和函数指针
- 仿函数(functor(仿函数), 或者称之为function object(函数对象))
仿函数(functor)是一个结构体或者类,只不过它重载了'()'操作符,使得这个结构体或者类可以像函数一样被调用。仿函数也叫函数对象。
- lambda函数
- 类成员函数指针或者类成员指针
二、std::function
可调用对象的定义方式比较多,但是函数的调用方式都是类似的。比如:经常遇到将可调用类型作为参数传递的情况,如果针对不同的可调用类型进行单独声明,则函数参数只能接收某种具体类型,这非常的不灵活,所以需要使用一种统一的方式保存可调用对象或者传递可调用对象,于是就有了std::function。
std::function是一个可调用对象包装器,它是一个类模板,它通过指定模版参数,用统一的方式来处理各种可调用对象。
std::function<return_type(parameter_types)> var_name;
其模板参数是函数签名:比如,void()表示一个函数,不接收参数,也不接收返回值;int(int,int)代表某函数,它接收两个参数并返回int值。假设我们要构建function<>实例,那么,由于模板参数先行指定了函数签名,因此指向的函数必须与之相符。即它应该接收指定类型的参数,返回值也必须可以转换为指定类型的可调用对象。
std::function是定义在 <functional>这个头文件中的,所以如果要想使用std::function则需要先包含这个头文件:#include <functional>
1.包装普通函数或者函数指针
#include <iostream>
#include <functional>
using namespace std;
int add(int a, int b) {
return a + b;
}
int main() {
//普通函数
function<int(int, int)> f = add;
cout << "sum is " << f(5, 8) << endl;
//定义一个函数指针,并将其指向一个函数
int (*fptr)(int, int);
fptr = add;
function<int(int, int)> ftr = fptr;
cout << "(fptr)sum is " << f(5, 79) << endl;
return 0;
}
2. 包装仿函数
下面给出了一个实例代码, 定义了一个class,名叫AddTwoNum,因为其重载了‘()’操作符,所以这个类定义的实例对象是一个仿函数 (或叫函数对象),在本例中,这个仿函数返回值是其两个int类型成员的数值之和。
#include <iostream>
#include <functional>
using namespace std;
class AddTwoNum {
public:
int operator()() { return a + b; }
AddTwoNum(int x, int y) :a(x), b(y) {}
AddTwoNum() { a = 0, b = 0; }
private:
int a;
int b;
};
int main() {
AddTwoNum add(100,200);
function<int(void)> f = add;
cout << f() << endl;
return 0;
}
3. 包装lamda函数
#include <iostream>
#include <functional>
using namespace std;
int main() {
function<int(int, int)> f1 ;
f1 = [](int a, int b) {
return a + b; };
cout << f1(10, 50) << endl;
return 0;
}
4. 包装类的静态成员函数
因为class的静态成员函数不需要类对象实例或者类对象指针也可以调用,所以function也可以包装类的静态成员函数。
#include <iostream>
#include <functional>
using namespace std;
class AddTwoNum {
public:
static int Sum(int a, int b) { return a + b; }
};
int main() {
function<int(int, int)> f = &AddTwoNum::Sum;
cout << f(50, 80) << endl;
return 0;
}
5. 包装类的普通成员函数
function是无法直接包装类的普通成员函数,因为类普通成员函数指针是需要类对象参与才能完成的,但是结合后面要介绍的bind()一起是可以实现包装类的普通成员函数的,这个在后面bind()那一节会看到。
std::function对象实例可被拷贝和移动,并且可以使用指定的调用特征来直接调用目标元素。当std::function对象实例未包含任何实际可调用实体时,调用该std::function对象实例将抛出std::bad_function_call异常。
三、std::bind()
std::bind()是一个通用的函数适配器,它也是包含在<functional>这个头文件里的。std::bind()的使用方式如下面所示,std::bind将可调用对象与其参数一起进行绑定,生成一个新的可调用对象来适应原对象的参数列表,绑定后的结果可以使用前面介绍的std::function保存。这时我们调用 newCallable,newCallable 就会调用 callable, 并用 arg_list 传递参数。
auto newCallable = bind(callable, arg_list);
另外,如果函数有多个参数,bind()可以绑定部分参数,其他的参数可以等到调用时指定。bind()在绑定参数时需要通过占位符std::placeholder::_x来决定bind()参数列表里所在的位置的参数在调用发生时将会属于第几个参数:_x表示外部传参时,调用者所传的实参列表里的第x个参数需要作为形参所在的这个位置的参数,下面举个例子:
下面这个例子是一个将参数列表里的参数打印出来的一个函数printF(),我们通过bind()绑定其部分参数,和参数顺序来生成几个不同的新的可调用对象。
- f1其实和原先的printF没有差别,参数个数和参数顺序都是一样的;
- f2有先绑定第三个参数,即第三个参数已经指定为一个固定的值--->3,所以新生成的可调用对象在调用时,只需要传入前两个参数的值即可;
- f3虽然也要像原先的printF()那样传入三个参数,可是由于placeholders有改变参数的顺序,已传入的第一个参数为例,因为f3的第1个参数有标明placeholders::_3,这就表示,调用f3()时传入的第3个参数会被作为原先可调用对象printF()的第一个参数。
- f4也是通过palceholders改变了原先的参数顺序,而且还先绑定了第2个参数的值为3
#include <iostream>
#include <functional>
using namespace std;
void printF(int x, int y, int z) {
cout << "x="<<x << ", y=" << y << ", z=" << z<<endl;
}
int main() {
auto f1 = bind(printF,placeholders::_1, placeholders::_2, placeholders::_3);
cout << "f1(1,2,3)-----> "; //x=1 ,y=2, z=3
f1(1, 2, 3);
auto f2 = bind(printF, placeholders::_1, placeholders::_2, 3);
cout << "f2(1,2) -----> "; //x=1 ,y=2, z is always 3
f2(1, 2);
auto f3 = bind(printF, placeholders::_3, placeholders::_1, placeholders::_2);
cout << "f3(1,2,3)-----> "; //x=3 ,y=1, z=2
f3(1, 2, 3);
//
auto f4 = bind(printF, placeholders::_2, 3, placeholders::_1);
cout << "f4(1,2) -----> "; //x=2 ,y is always 3, z=1
f4(1, 2);
return 0;
}
bind()绑定类的普通成员函数
bind()如果绑定的类的普通成员函数的话,需要传入该类的类对象或者类对象指针
#include <iostream>
#include <functional>
using namespace std;
class AddTwoNum {
public:
int Sum() { return a + b; }
AddTwoNum(int x, int y) :a(x), b(y) {}
AddTwoNum() { a = 0, b = 0; }
private:
int a;
int b;
};
int main() {
AddTwoNum add(100, 200);
//对象形式调用成员函数
auto f1 = bind(&AddTwoNum::Sum,add);
cout << f1() << endl;
//指针形式调用成员函数
auto f2 = bind(&AddTwoNum::Sum, &add);
cout << f2() << endl;
return 0;
}
bind()绑定类的静态成员函数
因为即使是在没有实例化的类对象的条件下,类的static成员函数也是可以被调用的,所以bind()绑定的是类的静态成员函数时,不用指定类对象或类对象指针。
#include <iostream>
#include <functional>
using namespace std;
class AddTwoNum {
public:
static int Sum(int a,int b) { return a + b; }
};
int main() {
auto f1 = bind(&AddTwoNum::Sum, placeholders::_1, placeholders::_2);
cout << f1(20,30) << endl;
return 0;
}