C++的函数对象

什么是函数对象

函数对象,又叫仿函数或函数子,英文是 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个原因:

  1. 函数对象可以有自己的状态,即利用它的成员变量来记录状态,这样可以实现一些复杂的功能。
    而函数没有办法记录状态,除非借助于全局变量。
    利用内部状态可以实现一些复杂的功能,比如,一个函数对象在多次的调用中可以共享这个状态。
    不过需要注意一点,在STL中都是传值的,所以函数对象也是作为一个值而被拷贝传入的;假如函数对象的内部状态在函数对象被调用时会发生改变,则很可能因为每次传入给调用者时,函数对象的内部状态都会被初始化而导致错误(见参考文献2中的例子)。所以,函数对象的使用者的行为最好不要依赖于函数对象的内部状态。当然,若函数对象的内部状态根本不会改变,那即使依赖也没有问题。

  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.

参考文献

  1. 《STL源码剖析》
  2. https://blog.csdn.net/stary_yan/article/details/51180741
  3. https://blog.csdn.net/y109y/article/details/82669620
  4. http://www.cplusplus.com/reference/functional/function/?kw=function
  5. http://www.cplusplus.com/reference/unordered_map/unordered_map/?kw=unordered_map
  6. http://www.cplusplus.com/reference/algorithm/sort/?kw=sort
  7. https://finixlei.blog.csdn.net/article/details/110267430

(完)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值