function是一种函数包装器,也叫做适配器。它可以对可调用对象进行包装,C++中的function本质就是一个类模板。
function类模板的原型:
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret:被包装的可调用对象的返回值类型。
Ret:被包装的可调用对象的形参类型。
function包装器可以对可调用对象进行包装,包括函数指针(函数名)、仿函数(函数对象)、lambda表达式、类的成员函数。
#include
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
// 函数名(函数指针)
std::function<int(int, int)> func1 = f;
cout << func1(1, 2) << endl;
// 函数对象
std::function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;
// lamber表达式
std::function<int(int, int)> func3 = [](const int a, const int b)
{return a + b; };
cout << func3(1, 2) << endl;
// 类的成员函数
std::function<int(int, int)> func4 = &Plus::plusi;
cout << func4(1, 2) << endl;
std::function<double(Plus, double, double)> func5 = &Plus::plusd;
cout << func5(Plus(), 1.1, 2.2) << endl;
return 0;
}
注意
包装时指明返回值类型和各形参类型,然后将可调用对象赋值给function包装器即可,包装后function对象就可以像普通函数一样使用了。
取静态成员函数的地址可以不用取地址运算符“&”,但取非静态成员函数的地址必须使用取地址运算符“&”。
包装非静态的成员函数时需要注意,非静态成员函数的第一个参数是隐藏this指针,因此在包装时需要指明第一个形参的类型为类的类型。
包装器可以解决模板实例化多份而造成的效率低下的问题。
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count: " << ++count << endl;
cout << "count: " << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
//函数指针
cout << useF(f, 11.11) << endl;
//仿函数
cout << useF(Functor(), 11.11) << endl;
//lambda表达式
cout << useF([](double d)->double {return d / 4; }, 11.11) << endl;
return 0;
}
由此可见,在这里由于我们对useF函数模板的第一个参数T传递了三种不同类型的函数,进而导致useF函数模板在编译阶段实例化出了三份,因为静态变量为三个不同的变量。
在使用三种不同函数作为实参传递并调用useF函数模板,实际上是分别调用了一次不同的useF函数,进而导致useF函数中的静态变量不是同一个,所以count只增加了一次。
但是由于我们所传的三种函数类型的返回值数据类型和形参数据类型是一样的,在执行useF函数中,其他数据类型也相同,所以根本就没必要实例化三份useF函数。
所以,包装器可以解决因为该情况而导致模板实例化多份造成效率低下的问题。
int main()
{
//函数名
function<double(double)> func1 = func;
cout << useF(func1, 11.11) << endl;
//函数对象
function<double(double)> func2 = Functor();
cout << useF(func2, 11.11) << endl;
//lambda表达式
function<double(double)> func3 = [](double d)->double {return d / 4; };
cout << useF(func3, 11.11) << endl;
return 0;
}
由于useF类模板中的静态变量相同,由此可以判断经过function包装过后,useF函数模板只实例化出了一份,这极大地提高了函数模板的效率。
当我们传递三种不同的函数类型并进行调用时,实际上就是调用了三次同一个useF函数,对静态变量count累加了三次
bind也是一种函数包装器,也叫做适配器。它可以接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,C++中的bind本质是一个函数模板。
bind函数模板的原型:
template <class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
参数说明:
调用bind的一般形式为:auto newCallable = bind(callable, arg_list);
说明:
arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置,比如_1为newCallable的第一个参数,_2为第二个参数,以此类推。
此外,除了用auto接收包装后的可调用对象,也可以用function类型指明返回值和形参类型后接收包装后的可调用对象。
int Plus(int a, int b)
{
return a + b;
}
int main()
{
//表示绑定函数plus参数分别由调用func1的第一,二个参数指定
std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
cout << func1(10, 20) << endl;
return 0;
}
第一个参数传入函数指针,后面传入绑定的参数列表依次是placeholders::_1和placeholders::_2,表示后续调用新生成的可调用对象时,第一个参数传给placeholders::_1,第二个参数传给placeholders::_2。此时绑定后生成的新的可调用对象的传参方式,和原来没有绑定的可调用对象是一样的,所以说这是一个无意义的绑定。
如果想把Plus函数的第二个参数固定绑定为10,可以在绑定时将参数列表的placeholders::_2设置为10。
#include
int Plus(int a, int b)
{
return a + b;
}
int main()
{
function<int(int)> func = bind(Plus, placeholders::_1,10);
cout << func(2) << endl;//12
return 0;
}
此时调用绑定后新生成的可调用对象时就只需传入一个参数,它会将该值与10相加后的结果进行返回。
对于下面Sub类中的sub成员函数,sub成员函数的第一个参数是隐藏的this指针,如果想要在调用sub成员函数时不用对象进行调用,那么可以将sub成员函数的第一个参数固定绑定为一个Sub对象。
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
//绑定固定参数
function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
cout << func(1, 2) << endl; //-1
return 0;
}
此时调用绑定后生成的可调用对象时,就只需要传入用于相减的两个参数了,因为在调用时会固定帮我们传入一个匿名对象给this指针。
如果想要将sub成员函数用于相减的两个参数的顺序交换,那么直接在绑定时将placeholders::_1和placeholders::_2的位置交换一下就行了。
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
//调整传参顺序
function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);
cout << func(1, 2) << endl; //1
return 0;
}
根本原因就是因为,后续调用新生成的可调用对象时,传入的第一个参数会传给placeholders::_1,传入的第二个参数会传给placeholders::_2,因此可以在绑定时通过控制placeholders::_n的位置,来控制第n个参数的传递位置。