c++11中的bind和mem_fn与function的应用

一、函数指针的衍生

在前面分析讨论了函数指针的问题,基本也明白了函数指针的工作的整个流程。既然函数指针应用如此方便,STL库一定会对进行封装。这也是经常看到的std::function,std::bind,std::mem_fn这几个模板类和函数模板。那么它们几个主要作用是什么呢?这个在官方文档说的很清楚。std::mem_fn用在类成员函数,你看它的名字mem就明白了,如果使用全局的,反而是无法通过的。那既然std::bind这也能做到这个功能,为啥还要弄个std::mem_fn呢?原因就是为了简单。就象工具一样,既有普遍适用的工具,也有专用的工具。专用工具在特定场合下更方便更顺手。
而std::function则是一个类模板,它是一个通用多态函数包装器。 std::function 的实例能存储、复制及调用任何可复制构造 (CopyConstructible) 的可调用 (Callable) 目标——函数、 lambda 表达式、 bind 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。
这下明白了吧,std::function是结果,而其它两个是实现这个结果的手段。其中一个手段比另外一个手段更专一,在专一的应用场景下,更方便简单。

二、std::bind和std::function

这里首先要介绍一下std::function的应用场景:
1、它可以用于普通函数
其中也包括静态和普通全局函数。
2、可以用于Lambda表达式
这个对模板的Lambda表达式也支持
3、可以用于类成员函数
又可以分为静态成员和普通类成员函数两种
4、可以用于模板函数
当然也包括模板类成员函数和普通模板函数
5、仿函数
就是对小括号的重载。

下面看一个完整的例子:

#include <functional>
#include <iostream>

void DisplayGreet()
{
    std::cout << "Global :Hello, world! "<<std::endl;
}
int DisplayNumber(int i)
{
    std::cout << "Global :number: " << i << std::endl;
    return 0;
}
template<typename T,typename N>
void DisplayTemplate(T t, N n)
{
    std::cout << "call DisplayTemplage:" << t + n << std::endl;
}

template <typename T = int>
struct FuncExample 
{
    template <typename T>
    T FuncGet(T i)
    {
        std::cout << "call template class :" << i + 10 << std::endl;
        return i;
    }

    template <typename T>
    static T FuncSet(T i)
    {
        std::cout << "call template class :" << i + 1 << std::endl;
        return i;
    }
    T operator()(T i,T j)
    {
        std::cout << "call functor:" <<i+j<< std::endl;
        return i + j;
    }
};

struct Foo {
    void display_greeting() {
        std::cout << "Hello, world.\n";
    }
    void display_number(int i) {
        std::cout << "number: " << i << '\n';
    }
    static int display_sdata(int i)
    {
        std::cout << "static function :" << i << std::endl;
        return 0;
    }
    int operator()(int i)
    {
        std::cout << "call functor non template:" << i << std::endl;
        return i;
    }
    int data = 7;
};

void TestFunc()
{
    //普通函数分别直接使用和绑定两种方式
    using namespace std::placeholders;
    std::function<void()> f1 = std::bind(DisplayGreet);
    f1();
    std::function<void()> f2 = DisplayGreet;
    f2();
    std::function<int(int)> f3 = std::bind(DisplayNumber, _1);
    f3(5);
    std::function<int(int)> f4 = DisplayNumber;
    f4(6);

    //类成员函数:普通和静态两种
    Foo f;
    std::function<void(int)> fc1 = std::bind(&Foo::display_number, &f, _1);
    fc1(5);
    std::function<int(int)> fs1 = Foo::display_sdata;
    fs1(6);
    std::function<int(int)> fs2 = &Foo::display_sdata;
    fs2(6);
    std::function<int(int)> fs3 = std::bind(Foo::display_sdata,_1);
    fs3(6);

    //Lambda表达式
    std::function<int(int)> fl =  [&](int i)->int {
        std::cout << "this is lambda:"<<i << std::endl;
        return 0;
    };
    fl(3);
    std::function<int(int,int)> flt = []<typename T>(T x, T y)->T { return x + y; };
    auto x = []<typename T>(T x, T y)->T { return x + y; };
    std::cout<<"lambda template" << flt(1, 2)<< " call x:"<< x(3,4)<<std::endl;

    //模板
    std::function<void(int, float)> ft1 = DisplayTemplate<int,float>;
    ft1(100,1.0f);
    FuncExample<int> fee;
    std::function<int(int)> ft2 = std::bind(&FuncExample<int>::FuncGet<int>,&fee,_1);
    ft2(6);
    std::function<int(int)> ft3 = std::bind(&FuncExample<int>::FuncSet<int>, _1);
    ft3(6);

    //绑定仿函数
    FuncExample<int> fe;//此处用来对比正常使用
    fe(1, 1);

    std::function<int(int, int)> ftor = FuncExample<int>();
    ftor(1,2);
    std::function<int(int)> ftor1 = Foo();
    ftor1(3);
}

int main()
{
    TestFunc();
    return 0;
}

这个运行结果没啥意义,就不贴上来了。
包括从下一小节的代码来看std::bind也是十分灵活的。基本上来说,把目前常用的使用方式都列举了过来,有兴趣的可以自己把代码拷到程序里跑一下,当然要支持c++20的编译器,因为里有Lambda模板。有一些相同类似的用法,都可以在几种方式种使用,这里可能有的地方就没有重写。但看到有的代码可以使用,其它都可以使用。比如std::bind绑定里可以不写类的对象实例,但是就需要在后面的使用函数中输入(看std::mem_fn中的bind)。

三、std::mem_fn

此函数模板应用比较简单,不用处理相关的参数信息,看一下具体的例程:

#include <functional>
#include <iostream>
 
struct Foo {
    void display_greeting() {
        std::cout << "Hello, world.\n";
    }
    void display_number(int i) {
        std::cout << "number: " << i << '\n';
    }
    int data = 7;
};
 
int main() 
{
    using namespace std::placeholders;
    Foo f;
 
    //std::mem_fn
    auto greet = std::mem_fn(&Foo::display_greeting);
    greet(&f);

    auto print_num = std::mem_fn(&Foo::display_number);
    print_num(&f, 42);

    auto access_data = std::mem_fn(&Foo::data);
    std::cout << "data: " << access_data(&f) << '\n';

    //std::bind ---1
    auto greetb = std::bind(&Foo::display_greeting, _1);
    greetb(&f);

    auto print_numb = std::bind(&Foo::display_number, _1, _2);
    print_numb(&f, 42);

    auto access_datab = std::bind(&Foo::data,_1);
    std::cout << "data: " << access_datab(&f) << '\n';

    //std::bind ---2
    auto greetb1 = std::bind(&Foo::display_greeting, &f);
    greetb1();

    auto print_numb1 = std::bind(&Foo::display_number, &f,_1);
    print_numb1(42);

    auto access_datab1 = std::bind(&Foo::data, &f);
    std::cout << "data: " << access_datab1() << '\n';

将其和std::bind对比一下就会发现,它的应用要简单很多。参数越多,越可以表现出来。当然,谁也不会没事写太多参数。但是,能省一点是一点吧。

四、总结

很多的技术和技巧,需要经常用。台上一分钟,台下十年功。编程也是如此,真正的天才惊艳的开发者,少而又少。大多数还是靠不断的积累知识,不断思考总结,不断的迭代重构代码,在这其中不断的成长。然后才能写出不断优秀的代码来。只想靠看看技术文章就想着某天能成为一个开发高手的人,基本上都是在浪费光阴,不动手亲自去编程开发,再好的技术也是一种样子,实践的过程就是验证对技术掌握的深度。想当然的认为掌握了,并不是真的掌握了。
越是学习,越是发现自己懂得太少,战战兢兢,如履薄冰。一起努力!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值