一、仿函数
STL中的仿函数,新标准叫做函数对象,其实可以理解成一种函数形式的调用机制,注意只是形式,它本身是通过重载小括号来实现的。当然,不用仿函数是否也可以实现相同功能呢?答案是肯定的,可以使用函数指针,c++11后还提供了更强大的std::function,还可以使用模板等功能。但为什么要用仿函数呢?
因为抽象?函数指针抽象是基本搞不起来的,而且函数指针会破坏整个STL的组件化,从设计上变成了广泛式的侵入式设计,这肯定为大佬儿们所不满。当然,由此也就使得设计的灵活性降低。
二、仿函数类型
仿函数按照操作数个数来划分可分为一元和二元仿函数;如果以功能划分则可分为算术运算、关系运算和逻辑运算三大类。
仿函数可以作为一种灵活的胶水进行工作,和适配器一起,共同完成整个STL组件的自由适配。可以把仿函数当作更细颗粒度的具体的适配功能动作对象。在STL中主要有以下的几种仿函数:
说明:以下图片出自侯捷先生的《STL源码剖析》
1、算术类

2、关系类

3、逻辑类

三、源码分析
其源码定义为:
template <class _Ty = void>
struct plus {
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _RESULT_TYPE_NAME;
constexpr _Ty operator()(const _Ty& _Left, const _Ty& _Right) const {
return _Left + _Right;
}
};
// STRUCT TEMPLATE minus
template <class _Ty = void>
struct minus {
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _RESULT_TYPE_NAME;
constexpr _Ty operator()(const _Ty& _Left, const _Ty& _Right) const {
return _Left - _Right;
}
};
// STRUCT TEMPLATE multiplies
template <class _Ty = void>
struct multiplies {
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _RESULT_TYPE_NAME;
constexpr _Ty operator()(const _Ty& _Left, const _Ty& _Right) const {
return _Left * _Right;
}
};
// STRUCT TEMPLATE equal_to
template <class _Ty = void>
struct equal_to {
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool _RESULT_TYPE_NAME;
constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const {
return _Left == _Right;
}
};
同样去除一些特殊定义宏,就是一个非常简单的操作符运算。其它也都类似。
四、例程
先看一下STL中的例程:
#include <iostream>
#include <functional> // std::plus
#include <algorithm>
void TestFunctors(int a, int b)
{
std::minus<int>()(a,b);
std::plus<int>()(a,b);
std::equal_to<int>()(a,b);
}
int main()
{
TestFunctors(1,3);
return 0;
}
然后再看一下自定义的相关仿函数的应用,看下面的代码:
template<typename T>
class CountPre
{
public:
CountPre(T t)
{
stand_ = t;
}
private:
T stand_;
public:
bool operator() (T tn)
{
return tn < stand_;
}
};
void TestCountPre()
{
std::vector<int> vec = { 1,2,3,4,5,6,7,8,9 };
std::cout << std::count_if(vec.begin(), vec.end(), CountPre<int>(5)) << std::endl;
}
int main()
{
TestCountPre();
return 0;
}
输出可以根据设置的标准值大小不断变化,当前的输出是4,也就是说比5小的有四个。
五、总结
仿函数目前看来优势是越来越小,他出生的时间还没有现在各种的复杂机制,只有这样才好。但仿函数最大的缺点在于在复杂调用情况下,特别是大量的模板函数调用下,无法跳转,给阅读代码带来更大的复杂度。虽然随着一些代码阅读器和编辑器越来越强大,这种情况有所改善,但还是一个感官上不太舒服的应用,各取所需吧。
正如常说的,没有最好,只有最合适!仅此而已!


本文介绍了C++ Standard Template Library (STL) 中的仿函数,也称为函数对象,主要用于提供一种函数调用的形式。仿函数可以通过重载小括号操作符来实现特定的功能,如算术、关系和逻辑运算。与函数指针相比,仿函数在STL中提供了更好的组件化和设计灵活性。文中列举了算术、关系和逻辑类的仿函数源码,并给出了使用示例,包括STL内置的和自定义的仿函数应用。尽管现代C++有std::function等替代方案,但仿函数在某些场景下仍具有价值。总结指出,仿函数在复杂模板调用中可能增加代码阅读难度,但在适当场景下仍具实用性。
789





