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