什么是函数对象
函数对象,又叫仿函数或函数子,英文是 function object 或 functor.
一个实现了函数调用操作符(即 operator()) 的类或结构体,就是仿函数。
operator () 就叫做函数调用操作符,英文是 function call operator.
下面是一个例子。
#include <algorithm>
#include <vector>
#include <unordered_map>
#include <string>
#include <iostream>
using namespace std;
bool cmp_func_ptr(const int& a, const int& b) {
return a > b;
}
struct mycmp {
bool operator()(const int& a, const int& b) {
return a > b;
}
};
struct myhash {
size_t operator() (const string& s) const noexcept {
return std::hash<std::string>()(s);
}
};
int main()
{
mycmp m1;
bool r1 = m1(10, 20); // 这里调用的是: 实例的operator()
cout << "m1(10, 20) = " << (r1 ? "true" : "false") << endl;
bool r2 = mycmp()(20, 10); // 这里调用的是: 临时对象的operator()
cout << "mycmp()(20, 10) = " << (r2 ? "true" : "false") << endl;
vector<int> vec{6, 3, 2, 1, 9, 8, 7, 5, 4};
std::sort(vec.begin(), vec.end(), mycmp()); // sort()的第3个参数是一个二元函数,故传递函数对象的临时对象
for (auto v : vec) { cout << v << " "; } cout << endl;
vec = {6, 3, 2, 1, 9, 8, 7, 5, 4};
std::sort(vec.begin(), vec.end(), m1); // 传临时对象可以,传普通对象当然也可以
for (auto v : vec) { cout << v << " "; } cout << endl;
vec = {6, 3, 2, 1, 9, 8, 7, 5, 4};
std::sort(vec.begin(), vec.end(), cmp_func_ptr); // 传函数指针给sort
for (auto v : vec) { cout << v << " "; } cout << endl;
std::unordered_map<string, int, myhash> umap; // unordered_map的第3个参数是函数对象类型
umap["Tom"] = 20;
umap["Kitty"] = 12;
for (auto v : umap) {
cout << v.first << " : " << v.second << endl;
}
cout << endl;
return 0;
}
注意,
- sort函数的第3个参数是二元函数(即有2个参数的函数),因此传递的是函数对象mycmp的临时对象
mycmp()
或普通对象m1
. - unordered_map 的模板的第3个参数是一个一元函数对象类型,因此不用加小括号.
STL标准库中已定义了一个函数对象,就是 std::function. 它的作用是把任意可调用元素(如函数或函数指针)封装进一个可拷贝的对象。下面给一个 std::function 的例子。
#include <iostream>
#include <unordered_map>
#include <string>
#include <functional>
using namespace std;
size_t hash_fn( const string & s ) {
return std::hash<string>()(s);
}
int main(int argc, char* argv[])
{
unordered_map<string,int,function<size_t (const string& s)>> umap(100, hash_fn); //需要把 hash_fn 传入构造函数
// unordered_map<string, int, decltype(&hash_fn)> umap(100, hash_fn ); // This is also OK
umap["Tom"] = 20;
umap["Kitty"] = 12;
for ( auto v : umap) {
cout << v.first << " " << v.second << endl;
}
return 0;
}
因为 unordered_map 的第3个模板参数是一个一元函数对象(即一个类或结构体的类型),而 hash_fn 是一个一元函数,所以在上例中,我们用 std::function<函数签名> 将 hash_fn 函数转为了函数对象。
另外,使用 decltype(&hash_fn) 可以起到和 function<size_t (const string& s)> 相同的效果。
为什么需要函数对象
有2个原因:
-
函数对象可以有自己的状态,即利用它的成员变量来记录状态,这样可以实现一些复杂的功能。
而函数没有办法记录状态,除非借助于全局变量。
利用内部状态可以实现一些复杂的功能,比如,一个函数对象在多次的调用中可以共享这个状态。
不过需要注意一点,在STL中都是传值的,所以函数对象也是作为一个值而被拷贝传入的;假如函数对象的内部状态在函数对象被调用时会发生改变,则很可能因为每次传入给调用者时,函数对象的内部状态都会被初始化而导致错误(见参考文献2中的例子)。所以,函数对象的使用者的行为最好不要依赖于函数对象的内部状态。当然,若函数对象的内部状态根本不会改变,那即使依赖也没有问题。 -
STL中不少容器的模板参数都是函数对象,而不是函数指针,因此我们需要使用函数对象。
例子有上面的 unordered_map 的模板参数,又比如,set的模板参数,priority_queue 的模板参数,等等。
那么,为什么STL中要使用函数对象呢?为了组件技术中的可适配性(adapability),即将某些修饰条件加诸其上而改变状态。
在侯捷的《STL源码剖析》的1.9.6节和第8章对此有所阐述。
下面就“利用函数对象的内部状态”给一个例子。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class NoLess
{
public:
NoLess(int min = 0):m_min(min){}
bool operator() (int value) const
{
return value >= m_min;
}
private:
int m_min;
};
template <typename T>
void print_vec(vector<T> vec) {
for (auto v : vec) {
cout << v << " ";
}
cout << endl;
}
int main() {
vector<int> vec1{1, 2, 3, 4, 5, 6};
vector<int> vec2 = vec1;
vec1.erase(remove_if(vec1.begin(), vec1.end(), NoLess(3)), vec1.end());
vec2.erase(remove_if(vec2.begin(), vec2.end(), NoLess(4)), vec2.end());
print_vec(vec1); // 1 2
print_vec(vec2); // 1 2 3
return 0;
}
上例中,2个函数对象的内部状态各不相同,一个是3,一个是4,而这2个函数对象都属于同一种类型的函数对象。
好了,关于函数对象的基础内容,大概就是这些了。而关于函数对象的使用的实际的例子,还可以再看看参考文献7.
参考文献
- 《STL源码剖析》
- https://blog.csdn.net/stary_yan/article/details/51180741
- https://blog.csdn.net/y109y/article/details/82669620
- http://www.cplusplus.com/reference/functional/function/?kw=function
- http://www.cplusplus.com/reference/unordered_map/unordered_map/?kw=unordered_map
- http://www.cplusplus.com/reference/algorithm/sort/?kw=sort
- https://finixlei.blog.csdn.net/article/details/110267430
(完)