包装器
function包装器
当我们在看到ret=func(x)
这样的语句时,func可能有多种类型,可能是函数名(函数指针),可能是仿函数,也可能是一个lambda表达式对象,因为这些都是可以被调用的类型。正是因为可以调用的类型太多,就会导致模板的使用效率底下。例如下面代码
#include<iostream>
template<class F,class T>
T func(F f, T x)
{
static int cnt = 0;
std::cout << ++cnt << std::endl;
std::cout << &cnt << std::endl;
return x;
}
int f1(int x)
{
return x / 2;
}
struct F
{
int f1(int x)
{
return x / 2;
}
};
int main()
{
//函数名
func(f1, 4);
//函数对象
func(F(), 4);
//lambda表达式
func([](int x)->int {return x / 2; }, 4);
return 0;
}
通过上面的程序验证,函数名、函数对象和lambda表达式分别示例化了三个不一样的函数模板,但是它们的返回值和形参列表都是相同的,于是可以利用这一点,用C++中的包装器解决实例化多份代码的问题
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
其中Ret
是返回值类型,而Args
调用函数的形参列表,通过返回值和形参,我们就可以将这些具有相似之处但是类型不一的可调用对象包装起来形成一个类型统一的可调用对象
#include<iostream>
#include<functional>
int f(int a, int b)
{
return a + b;
}
struct F
{
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)>f1 = f;
std::cout << f1(2, 3) << std::endl;
//函数对象调用
std::function<int(int, int)>f2 = F();
std::cout << f2(4, 5) << std::endl;
//类内非静态成员函数
std::function<int(int, int)>f3 = Plus::plusi;
std::cout << f3(5, 6) << std::endl;
return 0;
}
以上代码分别介绍了将函数名包装,将函数对象包装和将类内静态成员函数包装。还有一类:静态成员函数在包装方面需要专门拿出来讲
如果直接对其进行包装的话,会引发报错
在对类内非静态成员函数进行包装时需要前面需要加上取地址符号,这个是特殊规定(对于类内静态函数的包装,这个取地址符号可加可不加)
当加上取地址符号时,又出现了新的报错信息
这是因为类内的非静态成员函数中第一个形参是一个隐藏的类对象指针,但是在初始化包装器的时候并没有将这个指针传过去,这样在内部调用的时候就会出现参数不匹配的现象
正确使用类内非静态成员函数初始化包装器的方式有两种
无论是传递一个对象还是传递对象的地址都可以找到对象所对应的成员函数并进行调用
有了包装器,就可以解决之前模板效率低下的问题
#include<iostream>
#include<functional>
using namespace std;
template<class F, class T>
T func(F f, T x)
{
static int cnt = 0;
std::cout << ++cnt << std::endl;
std::cout << &cnt << std::endl;
return x;
}
int f1(int x)
{
return x / 2;
}
struct F
{
int operator()(int x)
{
return x;
}
};
int main()
{
function<int(int)>fa = f1;
function<int(int)>fb = F();
function<int(int)>fc = [](int x)->int {return x / 2; };
func(fa, 4);
func(fb, 4);
func(fc, 4);
return 0;
}
由于包装器的存在,对象fa
,fb
和fc
的类型都是相同的(这些对象的内部其实还是调用的函数,函数对象和lambda表达式),所以实例化的模板也只有一份
bind
std::bind
函数定义在头文件当中,是一个函数模板,它就像是一个函数包装其(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来"适应”原来对象的参数列表。总而言之,它可以改变可调用对象的参数列表中参数的个数和参数的顺序
`调用bind的一般形式:
auto newCallable = std::bind (callable, arg_list);
newCallable
自身就是一个可调用对象,arg_list
是一个以逗号作为分隔的参数列表,对应给定callable
的参数.当我们调用newCallable
时,newCallable
会调用callable
,并传给它arg_list
中的参数。
arg_list
中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示
newCallable
的参数,它们占据了传递给newCallable
的参数的“位置”。数值n表示生成的可调用对
象中参数的位置:_1为newCallable
的第一个参数,_2为第二个参数,以此类推。这些占位符被封装在命名空间placeholders中。
当需要调换传参的顺序时
#include<iostream>
#include<functional>
int sub(int a, int b)
{
return a - b;
}
int main()
{
std::cout << sub(1, 2) << std::endl;
std::function<int(int, int)>f1 = std::bind(sub, std::placeholders::_2, std::placeholders::_1);
std::cout << f1(1, 2) << std::endl;
return 0;
}
在语句std::function<int(int, int)>f1 = std::bind(sub, std::placeholders::_2, std::placeholders::_1);
中,第一个参数sub
表示是对函数sub进行包装 std::placeholders::_1
指代的就是传参时候放在第一个位置上的参数(比如f1(1,2),std::placeholders::_1就指代的是第一个参数1),std::placeholders::_2
指代的就是放在第二个位置上的参数,将std::placeholders::_1
和std::placeholders::_2
位置进行调换,就实现了传参时顺序的调换
当调整传递参数个数
调整传递参数个数主要是通过绑定参数来实现的,还是用上的代码为例
#include<iostream>
#include<functional>
using namespace std;
int sub(int a, int b)
{
return a - b;
}
int main()
{
auto f = bind(sub, 2, 1);//将两个参数全部绑定
cout << f() << endl;
auto f2 = bind(sub, 2,placeholders::_1);//只绑定了第一个形参,这样形参a就固定接收2
cout << f2(3) << endl;
return 0;
}
bind也可以用在非静态成员函数的包装上
这样传参的时候就可以少传递一个对象,写起来更加方便