《深入应用C++11》笔记-std::function和bind

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/WizardtoH/article/details/81356053

上一篇:《深入理解C++11》笔记-Unicode编码支持和原生字符串字面量

接下来的内容将会补充在《深入理解C++11》书中没有涉及的一些比较常用的特性,内容取自《深入应用C++11》。
在C++中,存在“可调用对象(Callable Objects)”这么一个概念。准确来说,可调用对象有如下几种定义:

  • 是一个函数指针。
  • 是一个具有operator()成员函数的类对象(仿函数)。
  • 是一个可被转换为函数指针的类对象。
  • 是一个类成员(函数)指针。
void func(void)
{
}

struct Foo
{
    void operator()(void)
    {
    }
};

struct Bar
{
    using fr_t = void(*)(void);

    static void func(void)
    {
    }

    operator fr_t(void)
    {
        return func;
    }
};

struct A
{
    int a_;

    void mem_func(void)
    {
    }
};

int main(void)
{
    void(*func_ptr)(void) = &func; // 1. 函数指针
    func_ptr();

    Foo foo;                        // 2. 仿函数
    foo();

    Bar bar;                        // 3. 可被转换为函数指针的类对象
    bar();

    void (A::*mem_func_ptr)(void)   // 4. 类成员函数指针
        = &A::mem_func;
    int A::*mem_obj_ptr             //    或者是类成员指针
        = &A::a_;
    A aa;
    (aa.*mem_func_ptr)();
    aa.*mem_obj_ptr = 123;
    return 0;
}

上面对可调用类型的定义里并没有包括函数类型或者函数引用(只有函数指针)。这是因为函数类型并不能直接用来定义对象;而函数引用从某种意义上来说,可以看做一个const的函数指针。C++中的可调用对象虽然具有比较统一的操作形式(除了类成员指针之外,都是后面加括号进行调用),但定义方法五花八门。这样在我们试图使用统一的方式保存,或传递一个可调用对象时,会十分烦琐。现在,C++11通过提供std::function和std::bind统一了可调用对象的各种操作。

std::function
std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们

#include <iostream>
#include <functional>

void func(void)
{
   std::cout << __FUNCTION__ << std::endl;
}

class Foo
{
public:
   static int foo_func(int a)
   {
      std::cout << __FUNCTION__ << "(" << a << ") ->: ";
      return a;
   }
};

class Bar
{
public:
   int operator()(int a)
   {
      std::cout << __FUNCTION__ << "(" << a << ") ->: ";
      return a;
   }
};

int main(void)
{
   std::function<void(void)> fr1 = func; // 绑定一个普通函数
   fr1();                                // func

   // 绑定一个类的静态成员函数
   std::function<int(int)> fr2 = Foo::foo_func;
   std::cout << fr2(123) << std::endl;   // foo_func(123) ->: 123

   Bar bar;
   fr2 = bar;     // 绑定一个仿函数
   std::cout << fr2(123) << std::endl;   // operator()(123) ->: 123

   return 0;
}

从上面我们可以看到std::function的使用方法,当我们给std::function填入合适的函数签名(即一个函数类型,只需要包括返回值和参数表)之后,它就变成了一个可以容纳所有这一类调用方式的“函数包装器”。

另外,std::function还能作为函数的入参以及函数指针:

class A
{
    std::function<void()> callback_;   // 函数指针

public:
    A(const std::function<void()>& f)  // 函数入参
        : callback_(f)
    {}

    void notify(void)
    {
        callback_();   // 回调到上层
    }
};

class Foo
{
public:
    void operator()(void)
    {
        std::cout << __FUNCTION__ << std::endl;
    }
};

int main(void)
{
    Foo foo;
    A aa(foo);
    aa.notify();

    return 0;
}

并且使用std::function有个好处就是能够将对象的成员函数作为参数,如果使用自定义的函数指针则会报错:

using pfunc = void(*)();

class A
{
    pfunc callback_;

public:
    A(pfunc f)
        : callback_(f)
    {}

    void notify(void)
    {
        callback_();   // 回调到上层
    }
};

class Foo
{
public:
    void operator()(void)
    {
        std::cout << __FUNCTION__ << std::endl;
    }
};

int main(void)
{
    Foo foo;
    A aa(foo);                // 编译失败
    aa.notify();

    return 0;
}

std::bind
std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。它主要有两大作用:

  • 将可调用对象与其参数一起绑定成一个仿函数。
  • 将多元(参数个数为n,n>1)可调用对象转成一元或者(n-1)元可调用对象,即只绑定部分参数。
void call_when_even(int x, const std::function<void(int)>& f)
{
    if (x % 2 == 0)
    {
        f(x);
    }
}

void output(int x)
{
    std::cout << x << " ";
}

void output_add_2(int x)
{
    std::cout << x + 2 << " ";
}

int main(void)
{
    auto func= std::bind(output, std::placeholders::_1);
    for (int i = 0; i < 10; ++i)
    {
        call_when_even(i, func);
    }
    std::cout << std::endl;   // 0 2 4 6 8

    func= std::bind(output_add_2, std::placeholders::_1);
    for (int i = 0; i < 10; ++i)
    {
        call_when_even(i, func);
    }
    std::cout << std::endl;

    return 0;                // 2 4 6 8 10
}

同样还是上面std::function中最后的一个例子,只是在这里,我们使用了std::bind,在函数外部通过绑定不同的函数,控制了最后的执行结果。我们使用auto类型保存std::bind的返回结果,是因为我们并不关心std::bind真正的返回类型(实际上std::bind的返回类型是一个stl内部定义的仿函数类型),只需要知道它是一个仿函数,可以直接赋值给一个std::function。当然,这里直接使用std::function类型来保存std::bind的返回值也是可以的。std::placeholders::_1是一个占位符,代表这个位置将在函数调用时,被传入的第一个参数所替代。可以通过以下的例子观察怎么使用占位符:

    std::bind(output, 1, 2)();    // 1 2
    std::bind(output, std::placeholders::_1, 2)(1); // 1 2
    std::bind(output, 2, std::placeholders::_1)(1); // 2 1                       
    std::bind(output, 2, std::placeholders::_2)(1);    // 编译失败,调用时没有第二个参数
    std::bind(output, 2, std::placeholders::_2)(1, 2); // 2 2
    std::bind(output, std::placeholders::_1, std::placeholders::_2)(1, 2);    // 1 2
    std::bind(output, std::placeholders::_2, std::placeholders::_1)(1, 2);    // 2 1

bind还有一个强大之处就是可以组合多个函数。假设要找出集合中大于5小于10的元素个数应该怎么做呢?

int main(void)
{
    std::vector<int> arr = { 0,1,2,3,4,5,6,7,8,9 };
    auto f = std::bind(std::logical_and<bool>(),
        std::bind(std::greater<int>(), std::placeholders::_1, 5),
        std::bind(std::less_equal<int>(), std::placeholders::_1, 10));
    int count = std::count_if(arr.begin(), arr.end(), f);
    std::cout << count << std::endl;

    return 0;
}

以上代码中,std::logical_and<bool>()是一个模板结构体成员函数,作用是返回传入的两个参数的逻辑与结果,例如:

std::logical_and<bool>()(1, 2);  // 返回1&&2的结果

std::logical_and<bool>()通过bind绑定了std::greater<int>()std::less_equal<int>(),就能得到他们两个返回值的逻辑与结果。而std::greater<int>()std::less_equal<int>()也是模板结构体成员函数,第一个参数是被比较值,绑定了第一个入参,第二参数是比较值,通过两个参数比较返回bool值。接着讲bind返回的函数,传入std::count_if,它的作用是遍历指定的一组数据并传入指定函数,函数返回true则count+1。这样就能得到集合中大于5小于10的元素个数了。

以上例子只是为了说明bind组合函数的方法,实际上用lambda函数实现更为简单:

int main(void)
{
    std::vector<int> arr = { 0,1,2,3,4,5,6,7,8,9 };
    int count = std::count_if(arr.begin(), arr.end(), [](int x) {
        return x > 5 && x < 10;
    });
    std::cout << count << std::endl;

    return 0;
}

下一篇:《深入应用C++11》笔记-线程std::thread

猜你喜欢

转载自blog.csdn.net/WizardtoH/article/details/81356053
今日推荐