function
- function 包装器
- 一些场景下模板的低效性
- 包装器 function 修复问题
- 包装成员函数的注意事项
- 一道例题
- function包装器的意义
- bind 包装器
- bind 包装器介绍
- bind 包装器可调整传参顺序
- bind 包装器可绑定固定参数
- bind 包装器的意义
C++11提供了多个包装器(wrapper,也叫适配器[adapter])。这些对象用于给其他编程接口提供更一致或更合适的接口。
function 包装器
一些场景下模板的低效性
请看下面的代码行:
result = f(q);
f 是什么?可以是函数名、函数指针、函数对象或有名称的lambda表达式。这些都是可调用的类型(callable type)。鉴于可调用的类型这么丰富,可能导致模板的效率很低。来看一个简单的案例:
template <class T,class F>
T use_f(T v, F f)
{
static int count = 0;
count++;
cout << " use_f count = " << count
<< ", &count = " << &count << endl;
return f(v);
}
class Fp
{
public:
Fp(double a = 1.0) : a_(a){}
double operator()(double p) { return a_ * p; }
private:
double a_;
};
class Fq
{
public:
Fq(double a = 1.0) : a_(a){}
double operator()(double q) { return a_ + q; }
private:
double a_;
};
模板use_f使用参数 f 表示调用类型:return f(v);
接下来,下面程序调用模板函数 use_f 6次
double dub(double x) { return 2.0 * x; }
double square(double x) { return x * x; }
int main()
{
double y = 1.21;
cout << "Function pointer dub : " << endl;
cout << " " << use_f(y, dub) << endl;
cout << "Function pointer square : " << endl;
cout << " " << use_f(y, square) << endl;
cout << "Function object Fp : " << endl;
cout << " " << use_f(y, Fp(5.0)) << endl;
cout << "Function object Fq : " << endl;
cout << " " << use_f(y, Fq(5.0)) << endl;
cout << "Lambda exepression 1:" << endl;
cout << " " << use_f(y, [](double u) {return u * u; }) << endl;
cout << "Lambda exepression 2:" << endl;
cout << " " << use_f(y, [](double u) {return u + u / 2.0; }) << endl;
return 0;
}
在每次调用中,模板参数T都被设置为double类型。那模板参数F呢?每次调用时,F都接受一个double值并返回一个double值,在6次的use_f()调用中,好像F的类型都相同,因此只会实例化模板一次?这是错误的。来看输出结果。
Function pointer dub :
use_f count = 1, &count = 00C0C140
2.42
Function pointer square :
use_f count = 2, &count = 00C0C140
1.4641
Function object Fp :
use_f count = 1, &count = 00C0C150
6.05
Function object Fq :
use_f count = 1, &count = 00C0C154
6.21
Lambda exepression 1:
use_f count = 1, &count = 00C0C474
1.4641
Lambda exepression 2:
use_f count = 1, &count = 00C0C478
1.815
模板函数 use_f()有一个静态成员 count,可根据它的地址确定模板实例化了多少次。有5个不同的地址,这表面模板 use_f() 有5个不同的变化。
接下来解释其中的原因:
首先,先看下面的调用:
use_f(y,dub);
其中的dub是一个函数名称,该函数接受一个double参数并返回一个double值。函数名是指针,因此参数F的类型为double(*)(double):一个指向这样的函数指针,即它接受一个double参数并返回一个double值。
下一个调用如下:
use_f(y,square);
第二个参数的类型也是double(*)(double),因此该调用使用的 use_f() 实例化与第一个调用相同。
在接下来的两个 use_f() 调用中,第二个参数为对象,F的类型分别为Fp 和Fq ,因此将为这些F值实例化两次。
最后两个调用将F 的类型设置为Lambda表达式使用的类型,本质上是仿函数,所以也会两个对象(匿名对象)。
包装器 function 修复问题
包装器的原型如下:
// 类模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
使用包装器 function 重写上述程序,使其只使用 use_f() 的一个实例而不是 5 个。上面程序中 函数指针、函数对象和lambda表达式有一个相同的地方。它们都接受一个double 参数并返回一个double值。可以说它们的调用特征标(call signatute) 相同。调用特征标是由返回类型以及括号括起并用逗号分隔的参数类型列表定义的,因此,这六个实例的特征标都是double(double)。
模板function是在头文件functional中声明的,它从调用特征标的角度定义了一个对象,可用于包装调用特征标相同的函数指针、函数对象或lambda表达式。例如,下面的声明创建一个名为 fdci 的function 对象,它接受一个char 参数和一个int 参数,并返回一个double 值:
std::function<double(char,int)> fdci;
然后,可以接受一个char 参数和一个 int 参数,并返回一个double值的任何函数指针、函数对象或lambda表达式赋给它。
在上面程序中,所以可调用参数的调用特征标都相同:double(double)。要修复程序减少实例化次数,可使用 function<double(double)> 创建六个包装器,用于表示6个函数、函数符和lambda。这样,在对use_f 的全部6次调用中,让F的类型都相同(function<double(double)>),因此只实例化一次。
代码如下:
int main()
{
double y = 1.21;
function<double(double)> f1 = dub;
function<double(double)> f2 = square;
function<double(double)> f3 = Fq(5.0);
function<double(double)> f4 = Fp(5.0);
function<double(double)> f5 = [](double u) {return u * u; };
function<double(double)> f6 = [](double u) {return u + u / 2.0; };
cout << "Function pointer dub : " << endl;
cout << " " << use_f(y, f1) << endl;
cout << "Function pointer square : " << endl;
cout << " " << use_f(y, f2) << endl;
cout << "Function object Fp : " << endl;
cout << " " << use_f(y, f3) << endl;
cout << "Function object Fq : " << endl;
cout << " " << use_f(y, f4) << endl;
cout << "Lambda exepression 1:" << endl;
cout << " " << use_f(y, f5) << endl;
cout << "Lambda exepression 2:" << endl;
cout << " " << use_f(y,f6) << endl;
return 0;
}
执行结果如下:
Function pointer dub :
use_f count = 1, &count = 007D05E8
2.42
Function pointer square :
use_f count = 2, &count = 007D05E8
1.4641
Function object Fp :
use_f count = 3, &count = 007D05E8
6.21
Function object Fq :
use_f count = 4, &count = 007D05E8
6.05
Lambda exepression 1:
use_f count = 5, &count = 007D05E8
1.4641
Lambda exepression 2:
use_f count = 6, &count = 007D05E8
1.815
从上面的输出可知,count的地址都相同,而count的值表明,use_f() 被调用了6次。这表明只有一个实例化,并调用了该实例6次,这缩小的可执行代码的规模。
包装成员函数的注意事项
成员函数分为静态成员函数和非静态成员函数。
class Plus
{
public:
Plus(int rate = 2) : _rate(rate)
{}
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _rate;
}
private:
int _rate;
};
静态成员函数的包装:
需要加个作用域
function<int(int, int)> f1 = Plus::plusi;
非静态成员的包装:
C++11规定,非静态成员的包装除了加域名外,还要加上&符号。
非静态成员函数的参数是默认带this指针的,所以包装器参数列表里要加上类名。
function<int(Plus,int, int)> f2 = &Plus::plusd;
int main()
{
function<int(int,int)> f1 = Plus::plusi;
function<int(Plus,int, int)> f2 = &Plus::plusd;
cout << f1(1, 1) << endl;//2
cout << f2(Plus(3),1, 1) << endl;//6,匿名对象方式使用
Plus p(3);
cout << f2(p, 1, 1) << endl;//6,创建了一个对象再去调用
return 0;
}
一道例题
这是力扣上一道逆波兰表达式的题,可以尝试用map结合包装器写一下。
150.逆波兰表达式求值
求解逆波兰表达式的步骤如下:
定义一个栈,依次遍历所给字符串。 如果遍历到的字符串是数字则直接入栈。
如果遍历到的字符串是加减乘除运算符,则从栈定抛出两个数字进行对应的运算,并将运算后得到的结果压入栈中。
所给字符串遍历完毕后,栈顶的数字就是逆波兰表达式的计算结果。
这是不用map和包装器的写法:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> s;
for(auto& i : tokens)
{
if(i == "+" || i == "-" || i == "*" || i == "/")
{
int right = s.top();
s.pop();
int left = s.top();
s.pop();
switch(i[0])
{
case '+' :
s.push(right + left);
break;
case '-' :
s.push(left - right);
break;
case '*' :
s.push(right * left);
break;
case '/' :
s.push(left / right);
break;
}
}
else
{
s.push(stoi(i));//字符串转整数
}
}
return s.top();
}
};
这是结合了map和包装器的写法:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> s;
map<string,function<int(int,int)>> op =
{
{"+",[](int a,int b){return a + b;}},
{"-",[](int a,int b){return a - b;}},
{"*",[](int a,int b){return a * b;}},
{"/",[](int a,int b){return a / b;}}
};
for(auto& i : tokens)
{
if(op.count(i))
{
int right = s.top();
s.pop();
int left = s.top();
s.pop();
s.push(op[i](left,right));
}
else
{
s.push(stoi(i));//字符转整数
}
}
return s.top();
}
};
function包装器的意义
将可调用对象的类型进行统一,便于对其进行统一化管理。
包装后明确了可调用对象的返回值和形参类型,更加方便使用者使用
bind 包装器
bind 包装器介绍
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);
模板参数说明:
fn:可调用对象。
args…:要绑定的参数列表:值或占位符。
调用bind的一般形式为:auto newCallable = bind(callable, arg_list);
解释说明:
callable:需要包装的可调用对象。
newCallable:生成的新的可调用对象。
arg_list:逗号分隔的参数列表,对应给定的callable的参数。当调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置,比如_1为newCallable的第一个参数,_2为第二个参数,以此类推。
在使用的时候占位符的时候需要指明作用域。
此外,除了用auto接收包装后的可调用对象,也可以用function类型指明返回值和形参类型后接收包装后的可调用对象。
bind 包装器可调整传参顺序
#include <iostream>
#include <functional>
using namespace std;
void Print(int a, int b)
{
cout << "a = " << a;
cout << " b = " << b << endl;
}
int main()
{
Print(10, 20);
function<void(int,int)> f1 = bind(Print, placeholders::_1, placeholders::_2);//未调整传参顺序
f1(10, 20);
//调整传参顺序
auto f2 = bind(Print, placeholders::_2, placeholders::_1);
f2(10, 20);
return 0;
}
上述代码中Print()分别打印传入的参数的值。
Print(10,20); 是普通的函数调用。
function<void(int,int)> f1 = bind(Print, placeholders::_1, placeholders::_2);//未调整传参顺序
f1(10, 20);
这段代码是先用bind包装器包装,但是未调整传参顺序,然后再用function包装,然后调用f1
//调整传参顺序
auto f2 = bind(Print, placeholders::_2, placeholders::_1);
f2(10, 20);
这段代码是用了bind包装器包装,且调整了传参顺序,然后用auto自动类型推导,然后调用f2
执行结果如下:
a = 10 b = 20
a = 10 b = 20
a = 20 b = 10
可以看到,bind包装器包装后,然后改变了传参顺序,起到了效果。
bind 包装器可绑定固定参数
在上面funtion包装器包装成员函数的时候有这么一段代码:
class Plus
{
public:
Plus(int rate = 2) : _rate(rate)
{}
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _rate;
}
private:
int _rate;
};
function包装非静态成员函数时因为非静态成员函数默认有个this,在包装和调用时还要额外加上一个对象
int main()
{
function<int(Plus, int, int)> f2 = &Plus::plusd;
cout << f2(Plus(3), 1, 1);//6
return 0;
}
如果结合bind包装器的话,在包装时可以把这个对象绑定住,那么在调用时只需传2个参数即可。
int main()
{
//function<int(Plus, int, int)> f2 = &Plus::plusd;
//cout << f2(Plus(3), 1, 1);//6
auto f2 = bind(&Plus::plusd, Plus(3), placeholders::_1,placeholders::_2);
cout << f2(1, 1);//6
return 0;
}
bind 包装器的意义
可以对函数参数的顺序进行灵活调整。
将一个函数的某些参数绑定为固定的值,让我们在调用时可以不用传递某些参数。