首页 > 编程语言 >C++11绑定器bind及function机制

C++11绑定器bind及function机制

时间:2022-10-30 23:14:46浏览次数:74  
标签:11 function 函数 int bind 绑定 对象 参数

前言

之前在学muduo网络库时,看到陈硕以基于对象编程的方式,大量使用boost库中的bindfunction机制,如今,这些概念都已引入至C++11,包含在头文件<functional>中。

本篇文章主要梳理C++绑定器相关的内容以及C++11中引入的function机制,其中绑定器主要有三种:bind1stbind2ndbind(C++11)。学完本篇内容,将对C++绑定器及function机制等的底层实现有深刻理解,那么我们开始说吧。

函数对象

首先说说函数对象,之所以说函数对象,是因为绑定器、function都涉及到该部分概念。函数对象实际上是类调用operator()()小括号运算符重载,实现像在“调用函数”一样的效果,因此还有个别名叫“仿函数”。函数对象示例代码如下:

class Print {
public:
    void operator()(string &s) { cout << s << endl; }
};

int main() {
    string s = "hello world!";
    Print print; //定义了一个函数对象print
    print(s);
    return 0;
}

上面代码print(s);语句,看似像函数调用,其实是类对象print调用其小括号运算符重载print.operator(string &s)print就是一个函数对象,至此对函数对象就有了基本的认识。

剖析绑定器bind1st、bind2nd

了解了函数对象,接下来我们说说绑定器,为什么需要绑定器?在使用STL时经常会遇到STL算法中需要传递某元函数对象,比如在写sort时,第三个参数决定了我们的排序规则,用来接收一个“比较器”函数对象,该函数对象是一个二元的匿名函数对象,形如greator<int>()或者less<int>()。二元函数对象的意思是,这个函数对象的小括号运算符重载函数接收两个参数,那么几元就表示接收几个参数。下面是库中自带的greaterless模板类的源码实现,可以看到是对小括号运算符重载的实现,sort第三个参数接收该模板类的二元匿名函数对象。

  template<typename _Tp>
    struct greater : public binary_function<_Tp, _Tp, bool>
    {
      _GLIBCXX14_CONSTEXPR
      bool
      operator()(const _Tp& __x, const _Tp& __y) const
      { return __x > __y; }
    };

  template<typename _Tp>
    struct less : public binary_function<_Tp, _Tp, bool>
    {
      _GLIBCXX14_CONSTEXPR
      bool
      operator()(const _Tp& __x, const _Tp& __y) const
      { return __x < __y; }
    };

再回到刚才的问题,那为什么需绑定器?由于STL接口的限制,有时我们拿到的函数对象和特定STL算法中要接收的函数对象在参数上并不匹配,意思就是需要传递一个一元函数对象,你有一个二元函数对象,那可以通过绑定器提前绑定二元函数对象的其中一个参数,使得最终返回的是一个一元函数对象,那么从二元函数对象到一元函数对象的转换过程,就需要绑定器去实现。

如STL中的泛型算法find_if,可用来查找可变长数组vector中符合某个条件的值(这个条件比如是要大于50,要小于30,要等于25等等)。其第三个参数需要传递一个一元函数对象,假如现在要找到第一个小于70的数,可将绑定器与二元函数对象结合,转换为一元函数对象后传递给find_if

我们知道系统自带的greater<int>()less<int>()模板类对象是二元匿名函数对象,所以需要通过绑定器将其转换为一元函数对象,可以通过bind1stbind2nd去绑定,顾名思义,前者对二元函数对象的第一个参数进行绑定,后者对二元函数对象的第二个参数进行绑定,两个绑定器均返回一元函数对象,用法如下:

sort(vec.begin(), vec.end(), greater<int>()); //从大到小对vector进行排序
find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 70));
find_if(vec.begin(), vec.end(), bind2nd(less<int>(), 70));

两个绑定器分别提前绑定了一个参数,使得二元函数对象+绑定器转换为一元函数对象:

operator()(const T &val)
greater a > b ====> bind1st(greater<int>(), 70) ====> 70 > b
less    a < b ====> bind2nd(less<int>(),    70) ====> a < 70

下面给出bind1st绑定过程图,二元函数对象绑定了第一个数为70,变为一元函数对象,传递给find_if泛型算法,此时find_if所实现的功能就是:找出有序降序数组中第一个小于70的数,所以find_if返回指向65元素的迭代器:

file:///Users/guochen/Notes/docs/media/16656563650484/16657214749366.jpgimage

以上就是绑定器的概念。因此需要绑定器的原因就很明显了,绑定器可以返回一个转换后的某元函数对象,用于匹配泛型算法

根据上面的理解,接下来实现一下bind1st,代码实现如下:

/*可以看到 自己实现的绑定器本质上也是个函数对象 调用operator()进行绑定*/
template<typename Compare, typename T>
class _mybind1st {
public:
    _mybind1st(Compare comp, T first) : _comp(comp), _val(first) {}
    bool operator()(const T &second) {
        return _comp(_val, second);
    }
private:
    Compare _comp;
    T _val;
};

/*实现bind1st 函数模板*/
//直接使用函数模板,好处是可以进行类型推演
template<typename Compare, typename T>
_mybind1st<Compare, T> mybind1st(Compare comp, const T &val) { //绑定器返回值_mybind1st为一元函数对象
    return _mybind1st<Compare, T>(comp, val);
}

上述代码中mybind1st绑定器第一个参数Compare comp是要绑定的二元函数对象,第二个参数val是在原有函数对象上绑定的值,最后绑定器调用_mybind1st模板函数对象的小括号运算符重载并返回该一元匿名函数对象,可以看到_mybind1st小括号运算符重载中已将绑定器mybind1st第二个参数val传递给了原本的二元函数对象Compare comp,因此原本绑定器接收的二元函数对象只需要处理第二个参数。所以绑定器返回的函数对象_mybind1st其实是在原本的函数对象上套了一层参数的新的函数对象,阅读上面的代码实现,就可更深刻的理解bind1st的底层原理。

与此同时,不难写出bind2nd的实现,顾名思义该绑定器是对第二个参数进行绑定,不过多赘述,贴出实现代码:

template<typename Compare, typename T>
class _mybind2nd {
public:
    _mybind2nd(Compare comp, T second) : _comp(comp), _val(second) {}
    bool operator()(const T &first) {
        return _comp(first, _val);
    }
private:
    Compare _comp;
    T _val;
};

template<typename Compare, typename T>
_mybind2nd<Compare, T> mybind2nd(Compare comp, const T &val) {
    return _mybind2nd<Compare, T>(comp, val);
}

根据上文,我们清楚了解到泛型算法find_if第三个参数接收一元函数对象,且该泛型算法功能是寻找第一个符合某条件的元素,我们对其补充实现,代码贴出:

/** 
 * 自己实现了find_if后发现其实绑定器返回的就是绑定后的函数对象
 * 使用绑定器的目的:就是将原本某元的函数对象转化为另一个元的函数对象
 * 说白了,绑定器还是对函数对象的一个应用
 **/
template<typename Iterator, typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare comp) {
    for(; first != last; ++first) {
        if(comp(*first)) { //调用comp的小括号运算符重载 一元函数对象 comp.operator()(*first)
            return first;
        }
    }
    return last;
}

此时要寻找vector中第一个小于70的数,就可以这样写:

auto it = my_find_if(vec.begin(), vec.end(), mybind1st(greater<int>(), 70));
cout << *it << endl; //打印vec中第一个小于70的数值

以上,围绕bind1stbind2nd以及函数对象等,展开讨论了绑定器bind1stbind2nd的实现原理,但是同时我们也发现其缺点,就是只能对二元函数对象进行绑定转换,让其转换为一元函数对象,那如果遇到很多元的函数对象,我们还得一个一个自己去实现吗?所以将boost库的boost::bind引入到了C++11标准库中,接下来我们介绍C++11的绑定器std::bind,它是对上述两种绑定器的泛化。支持任意函数对象(其实标准库中最多支持29元函数对象,不过这也足够使用了)。

补充:上面都是以函数对象为例,作为绑定器第一个参数传递,其实第一个参数可以是函数对象、成员函数、也可以是普通函数。

总结:绑定器本身是函数模板,绑定器第一个参数可能是普通函数、成员函数或函数对象等,返回的一定是函数对象。还有就是这两个绑定器在C++17中已移除,因此仅用于学习和理解绑定器,也方便我们对C++11引入的bind的学习。至于当前这两个绑定器如何实现对类成员函数的绑定等等我们也没必要去寻找答案了(我一开始也在努力寻找如何使用这两个绑定器去绑定类成员函数,但是发现bind可以很轻松地做到,当然如果大家知道怎么使用bind1stbind2nd绑定类成员函数,也可以评论告知我,感谢~)。

C++11 bind通用绑定器(函数适配器)

我们可将bind函数看作是一个通用的函数适配器,它接受一个可调用函数对象,生成一个新的可调用函数对象来“适应”原对象的参数列表。bind相比于bind1st和bind2nd,实现了“动态生成新的函数”的功能。简言之,可通过bind函数修改原函数并生成一个可以被调用的对象,类似于函数的重载,但是我们又不需要去重新写一个函数,用bind函数就可以实现。相信在上面讲bind1st和bind2nd时,大家对这些关于绑定器(函数适配器)的概念已经有所认知,我们直接看看如何用的吧。

绑定一个普通函数和函数指针

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;
int fun(int a, int b, int c, int d, int e) {
    return a + b - c + d - e;
}
int main() {
   int x = 1, y = 2, z = 3;
   auto g = bind(fun, x, y, _2, z, _1); //第一个参数&可省略 但最好写成&fun
   cout << g(11, 22) << endl; // fun(1, 2, 22, 3, 11) => 1+2-22+3-11
   // cout << bind(fun, x, y, _2, z, _1)(11, 22) << endl; //等价
}

g是有两个参数的二元函数对象,其两个参数分别用占位符placeholders::_2placeholders::_1表示,_2代表二元函数对象的第二个参数22_1代表二元函数对象的第一个参数11。这个新的可调用对象将它自己的参数作为第三个和第五个传递给fun,fun函数的第一个、第二个第四个参数分别被绑定到给定的值xyz上。

绑定一个类的静态成员函数与绑定全局函数没有任何区别,这里不做说明,可参考文章:[

标签:11,function,函数,int,bind,绑定,对象,参数
From: https://www.cnblogs.com/S1mpleBug/p/16793438.html

相关文章

  • 洛谷 P1153 点和线
    前置知识(1)求两条线段的交点方法1,运用高中的解析几何知识求.方法2,运用向量点积求.考虑向量叉乘。若\(\veca\times\vecb>0\),那么\(\vecb\)在\(\veca\)......
  • 【面试题】 为什么说 bind 的实现非常考验对原型链的理解?
    前言bind的实现其实非常考验对原型链的理解。bind和apply,call是JS修改this指向的三把利器......
  • 11.异常处理与模块
    异常处理当检测到⼀个错误时,解释器就⽆法继续执⾏了,反⽽出现了⼀些错误的提示,这就是所谓的"异常"。python提供了两个非常重要的功能来处理python程序在运行中出现的异常......
  • 115.distinct-subsequence 不同的子序列
    问题描述115.不同的子序列解题思路dp[i][j]表示考虑考虑t的前j个字符在s的前i个字符中的出现个数:if(s[i-1]==t[j-1])dp[i][j]=dp[i-1][j-1]+dp[i-......
  • Python3.11正式版,它来了!
    转载请注明出处❤️作者:测试蔡坨坨原文链接:caituotuo.top/b055fbf2.html你好,我是测试蔡坨坨。就在前几天,2022年10月24日,Python3.11正式版发布了!Python官方在2020年1月......
  • Java 从入门到放弃11 - 《Maven》
    说点题外话,没想到突然又重拾更新,在这期间学习了java的许多知识,还补充了sql语句,数据库相关的内容,由于时间关系,中间部分的内容都没有做相应笔记和博客,因为sql的语句还有中间......
  • P1195 口袋的天空
    最小生成树的板子;使得连通块的数量减小到k即可!数据有点水(printf("NoAnswer");根本没用到QAQ)。#include<bits/stdc++.h>usingnamespacestd;constintN=1e3+7;con......
  • [COMP2119] Searching - Building and Egg Problem
    DescriptionThereisabuildingwith$n$floorsandyouhave$m$eggs.Determinethelowestfloorthrownfromwhichaneggwillbreak.Ifaneggisbroken,it......
  • 【XSY3892】【hihocoder1147】时空阵(分层图dp)
    设\(dp(i,t,l)\)表示已经定好前\(i\)层,共有\(t\)个节点,其中第\(i\)层有\(l\)个节点。直接转移即可,注意一些细节:第\(1\)层只有\(1\)号节点。同层之间......
  • 删掉Win11 22h2文件管理器中的 “主文件夹”
    Win1122h2升级后,文件管理器左侧多了一个"主文件夹"的链接,没啥用还占位置,之前一直较忙没有管它,今天研究了一下,可以通过注册表干掉它:WindowsRegistryEditorVersion5.......