仿函数
为什么我们需要使用仿函数(仿函数解决了什么痛点)?
仿函数的优点和作用?
文章目录
为什么需要仿函数
场景一
-
场景引入
-
计算a数组里面全部的和,放在x变量里面。
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
template <typename InputIterator, typename T >
inline T accumulate(InputIterator first, InputIterator last, T init, T (*ptrA)(T, T)) {//函数指针
while (first != last) {
init = (*ptrA)(init, *first);
++first;
}
return init;
}
int funcA(int x, int y)
{
return x + y;
}
int main(void)
{
int a[5] = {2, 5, 7, 9, 11};
random_shuffle(&a[0], &a[5]);
int x = ::accumulate(&a[0], &a[5], 0, funcA);
cout << x << endl;
return 0;
}
- 这里就是计算a数组里面全部的和,放在x变量里面。
- 首先,这个函数的原型是T (*ptrA)(T x, T y);如果我想提升我处理数据的速度,让时间少浪费由于函数值传递引起的拷贝上,于是我需要这样写函数 T funcA(const int& x, const int& y)。这样写的话,我们原来的函数T (*ptrA)(T x, T y)就不能匹配这个函数,于是我们就要重载函数。还有就是效率问题,函数指针的调用,我们的电脑需要做很多工作,比如说保存当前的寄存器值,传递参数,返回值,返回到函数调用地方继续执行等。
- 幸运的是,这一切问题都可以通过仿函数来解决,如下:
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
template <typename InputIterator, typename T, typename FunObject>
inline T accumulate(InputIterator first, InputIterator last, T init, FunObject object) {//函数对象
while (first != last) {
init = object(init, *first);
++first;
}
return init;
}
template < typename T>
class Test {
public:
T operator()(const T& x, const T& y) {
return x + y;
}
};
int main(void)
{
int a[5] = {2, 5, 7, 9, 11};
random_shuffle(&a[0], &a[5]);
int x = ::accumulate(&a[0], &a[5], 0, Test<int>()); //仿函数作为函数的入参,只需要传入类对象即可,这里传入的是匿名对象
cout << x << endl;
return 0;
}
这样就解决了效率和函数重载的问题了。
场景二
- 假设我们现在有一个数组,数组中存有任意数量的数字,我们希望能够统计出这个数组中大于 10 的数字的数量,你的代码很可能是这样的:
#include <iostream>
using namespace std;
int RecallFunc(int *start, int *end, bool (*pf)(int)) {
int count=0;
for(int *i = start; i != end+1; i++) {
count = pf(*i) ? count+1 : count;
}
return count;
}
bool IsGreaterThanTen(int num) {
return num>10 ? true : false;
}
int main() {
int a[5] = {10,100,11,5,19};
int result = RecallFunc(a, a+4, IsGreaterThanTen);
cout<<result<<endl;
return 0;
}
- RecallFunc() 函数的第三个参数是一个函数指针,用于外部调用,而 IsGreaterThanTen() 函数通常也是外部已经定义好的,它只接受一个参数的函数。如果此时希望将判定的阈值也作为一个变量传入,变为如下函数:
bool IsGreaterThanThreshold(int num, int threshold) {
return num>threshold ? true : false;
}
- 虽然这个函数看起来比前面一个版本更具有一般性,但是它不能满足已经定义好的函数指针参数的要求,因为函数指针参数的类型是bool (*)(int),与函数bool IsGreaterThanThreshold(int num, int threshold)的类型不相符。如果一定要完成这个任务,按照以往的经验,我们可以考虑如下可能途径:
(1)阈值作为函数的局部变量。局部变量不能在函数调用中传递,故不可行;
bool IsGreaterThanThreshold(int num) {
int threshold; // 这里的threhold 没法获得外部的传参
return num>threshold ? true : false;
}
(2)全局变量。我们可以将阈值设置成一个全局变量。这种方法虽然可行,但不优雅,且容易引入 Bug,比如全局变量容易同名,造成命名空间污染
int threshold=10; // 定义全局变量
bool IsGreaterThanThreshold(int num) {
return num>threshold ? true : false;
}
(3)函数传参。这种方法我们已经讨论过了,多个参数不适用于已定义好的 RecallFunc() 函数。(除非你重写函数指针)
假设你设计的传参函数是这样的:
bool IsGreaterThanThreshold(int num, int threshold) {
return num>threshold ? true : false;
}
在此基础上,你必须重写 RecallFunc() 函数 。
int RecallFunc(int *start, int *end, bool (*pf)(int,int),int threshold) { 这里就需要引入新参数,来指threshold。同时需要重写函数指针,以使其符合IsGreaterThanThreshold 。
int count=0;
for(int *i = start; i != end+1; i++) {
count = pf(*i,threshold) ? count+1 : count;
}
return count;
}
-
这种方法扩展性较差,当函数参数有所变化,则无法兼容旧的代码,具体在第一小节已经阐述。正如上面的例子,在我们写代码时有时会发现有些功能代码,会不断地被使用。为了复用这些代码,实现为一个公共的函数是一个解决方法。不过函数用到的一些变量,可能是公共的全局变量。引入全局变量,容易出现同名冲突,不方便维护。
-
这时就可以使用仿函数了,写一个简单类,除了维护类的基本成员函数外,只需要重载 operator() 运算符 。这样既可以免去对一些公共变量的维护,也可以使重复使用的代码独立出来,以便下次复用。而且相对于函数更优秀的性质,仿函数还可以进行依赖、组合与继承等,这样有利于资源的管理。如果再配合模板技术和 Policy 编程思想,则更加威力无穷,大家可以慢慢体会。Policy 表述了泛型函数和泛型类的一些可配置行为(通常都具有被经常使用的缺省值)。
-
STL 中也大量涉及到仿函数,有时仿函数的使用是为了函数拥有类的性质,以达到安全传递函数指针、依据函数生成对象、甚至是让函数之间有继承关系、对函数进行运算和操作的效果。比如 STL 中的容器 set 就使用了仿函数 less ,而 less 继承的 binary_function,就可以看作是对于一类函数的总体声明,这是函数做不到的。
- 仿函数实现
#include <iostream>
using namespace std;
class IsGreaterThanThresholdFunctor {
public:
explicit IsGreaterThanThresholdFunctor(int t):threshold(t){}
bool operator() (int num) const {
return num > threshold ? true : false;
}
private:
const int threshold;
};
int RecallFunc(int *start, int *end, IsGreaterThanThresholdFunctor myFunctor) {
int count = 0;
for (int *i = start; i != end + 1; i++) {
count = myFunctor(*i) ? count + 1 : count;
}
return count;
}
int main() {
int a[5] = {10,100,11,5,19};
int result = RecallFunc(a, a + 4, IsGreaterThanThresholdFunctor(10));//仿函数作为函数的入参,只需要传入类对象即可,这里传入的是匿名对象
cout << result << endl;
}
- 这个例子应该可以让您体会到仿函数的一些作用:它既能像普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。于是仿函数提供了第四种解决方案:成员变量。成员函数可以很自然地访问成员变量,从而可以解决第一节“1.为什么要有仿函数”中提到的问题:计算出数组中大于指定阈值的数字数量。
仿函数是什么
具体定义
-
仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。
-
仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。
实例
我们先来看一个仿函数的例子。
直接调用仿函数
class StringAppend {
public:
explicit StringAppend(const string& str) : ss(str){}
void operator() (const string& str) const {
cout << str << ' ' << ss << endl;
}
private:
const string ss;
};
int main() {
StringAppend myFunctor2("and world!");
myFunctor2("Hello");// 隐式写法
// myFunctor2.operator()("Hello"); //显式写法
编译运行输出:
Hello and world!
仿函数作为函数入参
#include <iostream>
using namespace std;
class IsGreaterThanThresholdFunctor {
public:
explicit IsGreaterThanThresholdFunctor(int t):threshold(t){}
bool operator() (int num) const {
return num > threshold ? true : false;
}
private:
const int threshold;
};
int RecallFunc(int *start, int *end, IsGreaterThanThresholdFunctor myFunctor) {
int count = 0;
for (int *i = start; i != end + 1; i++) {
count = myFunctor(*i) ? count + 1 : count;
}
return count;
}
int main() {
int a[5] = {10,100,11,5,19};
int result = RecallFunc(a, a + 4, IsGreaterThanThresholdFunctor(10));//仿函数作为函数的入参,只需要传入类对象即可,这里传入的是匿名对象
cout << result << endl;
}
仿函数和STL
-
通过仿函数建立与stl沟通的桥梁,只为算法服务,当我需要对算法提出一些要求的时候,例如排序默认为从小到大,但是我需要由大到小进行排序,就需要用一般函数的形式或仿函数的形式告诉算法,实现第二个版本的算法。
-
为什么要把加减,比大小等等功能定义成一个函数或仿函数,因为需要将这些信息传入算法中。算法拿到这些东西之后才会做出相对应的改变。
算术仿函数
STL内建的算术类仿函数,支持加法、减法、乘法、除法、模数(余数)、否定运算。
template<class T> T plus<T> //加法仿函数
template<class T> T minus<T> //减法仿函数
template<class T> T multiplies<T> //乘法仿函数
template<class T> T divides<T> //除法仿函数
template<class T>T modulus<T> //取模仿函数
template<class T> T negate<T> //取反仿函数
struct plus : public binary_function<T, T, T>
{
T operator()(const T &x, const T &y) const return x y;
};
template <class T>
struct minus : public binary_function<T, T, T>
{
T operator()(const T &x, const T &y) const return x - y;
};
template <class T>
struct multiplies : public binary_function<T, T, T>
{
T operator()(const T &x, const T &y) const return x y;
};
template <class T>
struct divides : public binary_function<T, T, T>
{
T operator()(const T &x, const T &y) const return x y;
};
template <class T>
struct modulus : public binary_function<T, T, T>
{
T operator()(const T &x, const T &y) const return x $y;
};
template <class T>
struct negate : public unary_function<T, T>
{
T operator()(const T &x) const return -x;
};
- 例子
#include <iostream>
#include <functional>
using namespace std;
void test1()
{
negate<int>n;
cout << n(50) << endl;
}
void test2()
{
plus<int>p;
cout << p(10, 20) << endl;
}
int main()
{
test1();
test2();
std::cout << "Hello World!\n";
}
// -50
// 30
关系运算符
STL支持6种关系运算,每一种都是二元运算
等于,不等于,大于,大于等于,小于,小于等于
template<class T>
struct equal_to:public binary_function<T,T,bool>
{
bool operator()(const T&x,const T& y)const {return x==y;};
}
template<class T>
struct not_equal_to:public binary_function<T,T,bool>
{
bool operator()(const T& x,const T& y)const {return x!=y;}
};
template<class T>
struct greater:public binary_function<T,T,bool>
{
bool operator()(const T&x ,const T7 y)const {return x>y;}
};
template<class T>
struct less:public binary_function<T,T,bool>
{
bool operator()(const T&x ,const T7 y)const {return x<y;}
};
template<class T>
struct greater_equal:public binary_function<T,T,bool>
{
bool operator()(const T&x ,const T7 y)const {return x>=y;}
};
template<class T>
struct less_equal:public binary_function<T,T,bool>
{
bool operator()(const T&x ,const T7 y)const {return x<=y;}
};
逻辑运算符
template<class T> bool logical_and<T> //逻辑与
template<class T> bool logical_or<T> //逻辑或
template<class T> bool logical_not<T> //逻辑非
仿函数和智能指针
仿函数自定义删除器
//用来释放malloc出来的函数对象
template<class T>
class FreeFunc{
public:
void operator()(T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
//用来释放new[]出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
//用来释放new 出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
void operator()(T* ptr)
{
cout << "delete " << ptr << endl;
delete ptr;
}
};
//用来释放文件描述符的函数对象
template<class T>
class ClosefdFunc{
public:
void operator()(T* fd)
{
cout << "close fd" << fd << endl;
fclose(fd);
}
};
void test06(){
FreeFunc<int> Object1;
shared_ptr<int> sp1((int*)malloc(sizeof(int)*4), Object1); // 回调函数是可调用对象,可以是普通的函数名或者函数对象或者lambda表达式
DeleteArrayFunc<int> Object2;
shared_ptr<int> sp2(new int[4], Object2);
ClosefdFunc<FILE> Object3;
shared_ptr<FILE> sp3(fopen("myfile.txt","w"), Object3);
}
int main()
{
test06();
return 0;
}
- 输出结果:
close fd0x7ff94b4bfa90
delete[]0x220c21d1ae0
free:0x220c21d1770
智能指针中的仿函数
//智能指针的删除器:
template<class _Ty>
struct default_delete{
//......
//default deleter for unique_ptr,其中_Ptr是智能指针底层资源的指针
void operator()(_Ty *_Ptr) const _NOEXCEPT
{ // delete a pointer
delete _Ptr;
//默认删除器仅仅只做一件事,只调用delete进行资源的释放
}
//......
};
改进,针对这种特殊的情况,添加自定义的一个删除器保证资源释放完全:
template<typename Ty>
class Deleter{
public:
void operator()(Ty *ptr)const{
cout<<"Call a custom method !!!!! "<<endl;
delete []ptr;
}
};
int main(){
std::unique_ptr<int,Deleter<int>> ptr(new int[100]);//delete []ptr
return 0;
}
或者使用 default_delete来做删除器
// 可用default_delete来做删除器,default_.delete是标准库里的模板类。
void fun4()
{
cout << "detail5:: func4()" << endl;
shared_ptr<int> pi5(new int[100](), std::default_delete<int[]>());
}
改进,争对这种特殊的情况,添加自定义的一个删除器保证资源释放完全:
template<typename Ty>
class Deleter{
public:
void operator()(Ty *ptr)const{
cout<<"Call a custom method !!!!! "<<endl;
delete []ptr;
}
};
int main(){
std::unique_ptr<int,Deleter<int>> ptr(new int[100]);//delete []ptr
return 0;
}
或者使用 default_delete来做删除器
// 可用default_delete来做删除器,default_.delete是标准库里的模板类。
void fun4()
{
cout << "detail5:: func4()" << endl;
shared_ptr<int> pi5(new int[100](), std::default_delete<int[]>());
}