【C++11】C++11 中的std::function和std::bind

std::function 是通用多态函数封装器。 std::function 的实例能存储、复制及调用任何可调用 (Callable) 目标——函数、 lambda 表达式、 bind 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。

std::function 是一个泛化的函数,它可以代表任何函数的对象,可以被保存和复制。

 

人浅解:

将其当做std::string    、std::vector<> 、这样的类型就可以了。只不过其值为函数指针,但比函数指针更灵活。

因为std::function  是一种模板,所以要传入类型,就如std::vector<int>  传入类型int一样

不过,std::function传入的是函数类型  返回值 (参数类型) 如:std::function<void (int)>

 

1. 可调用对象

可调用对象有一下几种定义:

  • 是一个函数指针,参考 C++ 函数指针和函数类型
  • 是一个具有operator()成员函数的类的对象;
  • 可被转换成函数指针的类对象;
  • 一个类成员函数指针;

C++中可调用对象的虽然都有一个比较统一的操作形式,但是定义方法五花八门,这样就导致使用统一的方式保存可调用对象或者传递可调用对象时,会十分繁琐。C++11中提供了std::function和std::bind统一了可调用对象的各种操作。

不同类型可能具有相同的调用形式,如:

 

// 普通函数
int add(int a, int b){return a+b;} 

// lambda表达式
auto mod = [](int a, int b){ return a % b;}

// 函数对象类
struct divide{
    int operator()(int denominator, int divisor){
        return denominator/divisor;
    }
};

上述三种可调用对象虽然类型不同,但是共享了一种调用形式:

 

int(int ,int)

std::function就可以将上述类型保存起来,如下:

 

std::function<int(int ,int)>  a = add; 
std::function<int(int ,int)>  b = mod ; 
std::function<int(int ,int)>  c = divide(); 

2. std::function

  • std::function 是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。
  • 定义格式:std::function<函数类型>。
  • std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。

3. std::bind

可将std::bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:

  • 将可调用对象和其参数绑定成一个防函数;
  • 只绑定部分参数,减少可调用对象传入的参数。

3.1 std::bind绑定普通函数

 

double my_divide (double x, double y) {return x/y;}
auto fn_half = std::bind (my_divide,_1,2);  
std::cout << fn_half(10) << '\n';                        // 5
  • bind的第一个参数是函数名,普通函数做实参时,会隐式转换成函数指针。因此std::bind (my_divide,_1,2)等价于std::bind (&my_divide,_1,2);
  • _1表示占位符,位于<functional>中,std::placeholders::_1;

3.2 std::bind绑定成员函数

 

struct Foo {
    void print_sum(int n1, int n2)
    {
        std::cout << n1+n2 << '\n';
    }
    int data = 10;
};
int main() 
{
    Foo foo;
    auto f = std::bind(&Foo::print_sum, &foo, 95, std::placeholders::_1);
    f(5); // 100
}
  • bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址。
  • 必须显示的指定&Foo::print_sum,因为编译器不会将对象的成员函数隐式转换成函数指针,所以必须在Foo::print_sum前添加&;
  • 使用对象成员函数的指针时,必须要知道该指针属于哪个对象,因此第二个参数为对象的地址 &foo;

 

3.3 参数绑定

bind其调用形式如下:

auto newCallable=bind(callable,arg_list);


bind的第一个参数为一个可调用对象,可调用对象是指可以对其使用调用运算符()的对象。
可调用对象常用的有函数、函数指针、重载了函数调用运算符的类和lambda表达式


arg_list是调用对象的参数列表,可以包含 _ 1, _ 2等这样的占位符,用于占据调用对象的参数位置,数字代表着是第几个未定的占位参数,占位符被定义在,命名空间placeholders中。也可以包含被绑定对象的参数。arg_list应该和被绑定对象的参数一样多。

int sum(int a, int b, int c) {
    if (a > b)return a + c;
    return b + c;
}
auto add = bind(sum, _1, _2, 10);


这样就将sum绑定由bind新生成的一个调用sum的对象上;
_ 1表示新对象中的第一个参数,是一个占位符。
也就是说,实际上,这个bind会add( _ 1, _ 2)会被映射成为sum( _ 1, _ 2, 10),此时add的参数就会代替原来的占位符成为调用sum的参数,当然前提是两者的类型要匹配。
比如:add(20,10)实际调用为sum(20,10,10),结果为30;

参数顺序可换

#include<bits/stdc++.h>
using namespace std;
using namespace placeholders;
int sum(int a, int b, int c)
{
    if (a > b)return a + c;
    return b + c;
}
int main(void)
{
    auto add  = bind(sum, _1, _2, 10);
    auto add2 = bind(sum, _2, _1, 10);
    int t = add(20, 10), t1 = add2(10, 20);
    cout << t << " " << t1 << endl;
    return 0;
}



bind也可以换原来参数的顺序,因为实际在调用新对象时,我们传递给新对象的参数实际就是那些占位符占据的位置的参数,所以上面调用情况如下:
add(20,10) 时,参数20对应占位符1,参数10对应占位符2,故实际调用为sum(20,10,10);
add2(10,20)时,参数10对应占位符1,参数20对应占位符2,故实际调用为sum(20,10,10);
从而重排了参数顺序。

原文链接:https://blog.csdn.net/Enterprise_/article/details/102806998

 

可以包含 _ 1, _ 2等这样的占位符,用于占据调用对象的参数位置,数字代表着是第几个未定的占位参数

void printValue(int value1, int value2, int value3)
{
    cout << value1 << endl;
    cout << value2 << endl;
    cout << value3 << endl;
}


void testFunc(std::function<void(int, int)> printFunc, int value1, int value2)
{
    printFunc(value1, value2);
}

int main()
{
    auto newFunc1 = std::bind(printValue, placeholders::_1, placeholders::_2, 123);
    testFunc(newFunc1, 111, 222); //111 222 123

    auto newFunc2 = std::bind(printValue, placeholders::_1, 123, placeholders::_2);
    testFunc(newFunc2, 111, 222);//111 123 222
    system("pause");
}

 

3.3 绑定一个引用参数

默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但是,与lambda类似,有时对有些绑定的参数希望以引用的方式传递,或是要绑定参数的类型无法拷贝。

 

#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
#include <sstream>
using namespace std::placeholders;
using namespace std;

ostream & print(ostream &os, const string& s, char c)
{
    os << s << c;
    return os;
}

int main()
{
    vector<string> words{"helo", "world", "this", "is", "C++11"};
    ostringstream os;
    char c = ' ';
    for_each(words.begin(), words.end(), 
                   [&os, c](const string & s){os << s << c;} );
    cout << os.str() << endl;

    ostringstream os1;
    // ostream不能拷贝,若希望传递给bind一个对象,
    // 而不拷贝它,就必须使用标准库提供的ref函数
    for_each(words.begin(), words.end(),
                   bind(print, ref(os1), _1, c));
    cout << os1.str() << endl;
}

4. 指向成员函数的指针

通过下面的例子,熟悉一下指向成员函数的指针的定义方法。

 

#include <iostream>
struct Foo {
    int value;
    void f() { std::cout << "f(" << this->value << ")\n"; }
    void g() { std::cout << "g(" << this->value << ")\n"; }
};
void apply(Foo* foo1, Foo* foo2, void (Foo::*fun)()) {
    (foo1->*fun)();  // call fun on the object foo1
    (foo2->*fun)();  // call fun on the object foo2
}
int main() {
    Foo foo1{1};
    Foo foo2{2};
    apply(&foo1, &foo2, &Foo::f);
    apply(&foo1, &foo2, &Foo::g);
}
  • 成员函数指针的定义:void (Foo::*fun)(),调用是传递的实参: &Foo::f;
  • fun为类成员函数指针,所以调用是要通过解引用的方式获取成员函数*fun,即(foo1->*fun)();

参考



作者:georgeguo
链接:https://www.jianshu.com/p/f191e88dcc80
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

std::function 基本用法

std::function 是一个泛化的函数,它可以代表任何函数的对象,可以被保存和复制。本实验我们将练习 std::function 的定义和调用。

在 code1 目录下新建 code4.cpp 文件,编写下面这些代码:

#include <iostream>
#include <functional>

void test1(){std::cout<<"function"<<std::endl;}

int test2(int i){ return i; }

int test3(int i, int j){ return i+j; }

struct A{
    void foo(int i){ std::cout<<i<<std::endl; }
};

int main() {
    std::function<void()> fn1 = std::bind(test1);
    std::function<int(int)> fn2 = std::bind(test2, std::placeholders::_1);
    std::function<int(int, int)> fn3 = std::bind(test3, std::placeholders::_1, std::placeholders::_2);
    std::function<int(int)> fn4 = std::bind(test3, 3, std::placeholders::_1);
    std::function<int()> fn5 = std::bind(test3, 3, 4);

    A a;
    std::function<void(int)> fn6 = std::bind(&A::foo, &a, std::placeholders::_1);

    fn1();
    std::cout<<fn2(1)<<std::endl;
    std::cout<<fn3(2, 3)<<std::endl;
    std::cout<<fn4(3)<<std::endl;
    std::cout<<fn5()<<std::endl;
    fn6(8);
}

 

编译和运行代码:在 build 目录下执行

g++ ../code4.cpp -o code4 -std=c++11 && ./code4

 

输出结果:

function
1
5
6
7
8

 

以下面这个语句为例。

std::function<int(int)> fn2 = std::bind(test2, std::placeholders::_1)

 

std::function<int(int)> 表示的是一个返回类型为 int ,参数类型为 int 的函数,它的实例化是通过 std::bind 来实现的。注意这里的 std::placeholders::_1 表示的是第一个参数占位符,std::placeholders::_2 则表示的是第二个参数的占位符。参数占位符的目的是占位,在后面真正调用 std::function 的时候才填真正的参数。

如果不用占位符那么就需要在构造 std::function 的时候填充好参数,后面就不用再填充参数了,比如在如下语句中,

std::function<int()> fn5 = std::bind(test3, 3, 4)

 

bind test3 时,我们就直接把参数传进去了,后面调用的时候就不用再传参数了。

这里也要注意 bind 成员函数的用法,需要使用对象的指针,比普通函数多一个对象指针的参数。

posted on 2022-10-04 01:26  bdy  阅读(44)  评论(0编辑  收藏  举报

导航