headPic

C++: functional

OAP

C++

1. 前言

在C中调用一个函数,要么直接用函数名,要么用函数指针,但函数指针玩一些骚操作的时候灵活性有限。而functionbind作为C++11的两个模板类,能够封装类似函数指针的可调用对象,提供一致的调用接口,比原生的函数指针要更加灵活,也减少了许多心智负担。

👉 可调用对象

可调用对象:

  • 普通函数
  • 类成员函数
  • 类静态函数
  • 仿函数/函数对象
  • lambda
  • bind创建对象

这里分类并不十分精准,只是个人经验,更标准的分法是五种:

  • 函数
  • 函数指针
  • lambda
  • bind创建的对象
  • 仿函数

两个模板类都在头文件<functional>中被定义。

2. 使用

2.1 普通函数

以add和sub两个函数为例:

 1int add(int x, int y) {
 2    return x + y;
 3}
 4
 5int sub(int x, int y) {
 6    return x - y;
 7}
 8
 9function<int(int, int)> func = add;
10cout << func(1, 2) << endl; // 3
11
12func = sub;
13cout << func(1, 2) << endl; // -1

function模板在传入函数类型后就可以直接引用不同的函数,只要函数类型相同即可。

再比如回调函数:

1int callback(function<int(int, int)> func, int x, int y) {
2   return func(x, y);
3}
4
5cout << callback(add, 1, 2) << endl; // 3

bind则是起到绑定参数的作用,function配合bind则可以完成延迟调用。首先,bind的基本使用如下:

1void print(const string& s1, const string& s2) {
2    cout << s1 << " " << s2 << endl;
3}
4
5string s1{"hello"}, s2{"world"};
6
7function<void()> f1 = bind(print, s1, s2);
8f1(); // hello world

这里很有意思,bind把print的参数绑定了,所以function对象的模板参数是void(),而非print的函数类型。bind还可以只绑定部分参数:

1string s3{"hi"};
2
3function<void(const string&)> f2 = bind(print, placeholders::_1, s2);
4f2(s3); // hi world

placeholders::_1则是一个占位符,表示该参数在调用时被决定。 这里也可以看出来,bind返回的可调用对象的确“改变了函数的参数列表”,也比较符合bind的字面意思:绑定参数到可调用对象上

2.2 类成员函数

类成员函数则是需要一个实例对象才能调用(想想this),因此在使用bind包装时,要传入实例对象作为第一个参数。

 1class foo {
 2public:
 3    int add(int x, int y) {
 4        return x + y;
 5    }
 6    static int sub(int x, int y) {
 7        return x - y;
 8    }
 9};
10
11foo foo1;
12auto f3 = bind(&foo::add, foo1, 10, 20);
13cout << f3() << endl; // 30

第一个参数仍然是函数地址,但不同的是多一个取地址符号,类成员函数通过&来标明这是一个成员函数,第二个参数则是实例对象。 foo1如果要以引用的方式传递的时候,或者说要更改foo1内部数据的时候,要以引用(std::ref)的方式传递。

2.3 类静态函数

static函数属于类,所以无需对象也可调用,因此不用添加&。

1auto f4 = bind(foo::sub, 10, 20);
2cout << f4() << endl;  // -10

bind对于预先绑定的参数是pass-by-value的,要传递引用则需要使用std::ref或者std::cref,前者是传引用,后者是传const引用。

 1void add1(int &a) {
 2    ++a;
 3}
 4
 5int a = 1;
 6auto f5 = bind(add1, a);
 7f5();
 8cout << a << endl; // 1
 9
10auto f6 = bind(add1, ref(a));
11f6();
12cout << a << endl; // 2

用std::ref则是传递引用,std::ref返回一个包装好的引用对象reference_wrapper,能够转换成被引用值的引用类型(内部本质就是传地址的)

lambda和仿函数也是同理,无非是仿函数需要一个实例对象,此处不再赘述。

1// lambda
2function<bool(const int&)> f1 = [](const int&a) { return a; };
3// 仿函数,这里的foo()是创建临时对象,而非调用函数
4function<bool(const int&)> f2 = foo();

2.4 访问类的成员

例子来自cpp reference,感觉很奇怪:

 1#include <iostream>
 2#include <functional>
 3
 4class Foo {
 5public:
 6    int num_;
 7
 8    Foo(int num) : num_(num) {} // 构造函数初始化 num_
 9};
10
11int main() {
12    Foo foo(10);
13
14    // 创建一个 std::function 对象 f_num,它可以接受 Foo 类型的引用并返回 int 类型的值
15    std::function<int(const Foo&)> f_num = &Foo::num_;
16
17    // 通过传入 foo 对象的引用,使用 f_num 来获取 foo 对象的 num_ 成员变量的值
18    std::cout << "num_: " << f_num(foo) << '\n'; // num_: 10
19
20    return 0;
21}

没想到居然还能访问数据成员,倒是一种用法。

3. 参考

  1. https://www.bilibili.com/video/BV1bm41197XV/?spm_id_from=333.788&vd_source=71ee144274d993f1c946fc98badf272d
  2. https://www.cnblogs.com/Philip-Tell-Truth/p/5814213.html
  3. https://en.cppreference.com/w/cpp/utility/functional/function