C++11中的std::bind简介

0

std::bind是一个函数模板,它可以将一个可调用对象包装成另一个可调用对象,并预先绑定一些参数或者占位符。 例如:

// 有一个函数f1声明如下
int f1(int, double, char);
// 通过std::bind绑定参数或修改参数顺序
auto f2 = std::bind(f1, 3, std::placeholders::_2, std::placeholders::_1);

此时,调用f2(3.1416, 2.7183)等同于调用f1(3, 2.7183, 3.1416)。 如果需要多个可调用对象,且只有默认参数或参数顺序不同,就可以通过std::bind来生成这些可调用对象,而不是重新编写一个函数。

std::placeholders

std::placeholders是std命名空间的一个子命名空间,其中定义了 _1, _2, …, _N 等一组对象,其中N是依据不同实现而不同的最大值。 这些对象是在std::bind中使用的占位符,这些占位符指明了std::bind生成的新可调用对象中的参数位置与原可调用对象参数位置的对应关系。 例如:

#include <iostream>
#include <functional>
int f(int a, double d)
{
    std::cout << a << ' ' << d << std::endl;
}
int main()
{
    using namespace std::placeholders;
    auto g = std::bind(f, _2, _1);
    g(1.2, 3); // 等同于调用f(3, 1.2)
    return 0;
}

N的最大值可能因实现不同而不同,可以通过下述方法来判断某个_n是不是一个合法的占位符。

#include <iostream>
#include <functional>
int main()
{
    using namespace std::placeholders;
    std::cout << std::is_placeholder<decltype(_1)>::value << "\n";
    std::cout << std::is_placeholder<decltype(_10)>::value << "\n";
    std::cout << std::is_placeholder<decltype(_20)>::value << "\n";
    std::cout << std::is_placeholder<int>::value << "\n";
    return 0;
}

对于占位符_n,std::is_placeholder<decltype(_n)>::value返回n;对于其他的类型则返回0。 实际上,如果输入的n大于了最大值N,那么编译器会因为找不到该对象而停止工作,而不是在运行时才返回0。 即使这样,也可以通过二分的方法快速找到N。 在博主的VS2015中,这个N是20(你根本就不需要编译,VS的智能提示会告诉你这个值);GCC 4.8中是29。

std::bind

在C++11之前,也有像std::bind1st、std::bind2nd这种参数绑定器,但由于使用不灵活且作用及其有限(C++11中被弃用,C++17中被移除),此处不再涉及。 使用std::bind可以以更优美的方式来实现参数绑定,通过它可以绑定普通函数、类静态函数、类成员函数等。

  • 普通函数
  • 类的静态函数
  • 类的成员函数
  • 重载函数
  • 类的成员重载函数
  • 虚函数
  • 模板函数
  • 仿函数(函数对象)
#include <iostream>
#include <functional>
int global_func(int a, int b) {
    return a + b;
}
class C {
public:
    static int static_class_func(int a, double b) {
        return a;
    }
    char class_func(char c) {
        return c;
    }
    char class_overload_func(char c) {
        return c;
    }
    int class_overload_func(int c) {
        return c;
    }
    virtual int virtual_func(int v) {
        return v - 1;
    }
};
class D : public C {
public:
    int virtual_func(int v) {
        return v + 1;
    }
};
int global_overload_func(int a, int b) {
    return a + b;
}
double global_overload_func(double a, double b) {
    return a + b;
}
template<typename T>
T template_func(T t) {
    return t;
}
int main()
{
    using namespace std::placeholders;
    // 1. bind普通函数
    auto f1 = std::bind(global_func, 1, _1);
    std::cout << f1(7) << '\n'; // 8

    // 2. 类的静态函数
    auto f2 = std::bind(&C::static_class_func, _2, _1);
    std::cout << f2(3.14, 1) << '\n'; // 1

    // 3. 类的成员函数
    // bind类的成员函数需要在第一个参数位置增加一个类的指针参数或占位符
    C c;
    auto f3 = std::bind(&C::class_func, &c, 'c');
    std::cout << f3() << '\n'; // c

    // 4. 重载函数
    auto f4 = std::bind(static_cast<int(*)(int, int)>(global_overload_func), _1, _2);
    std::cout << f4(1, 2) << '\n'; // 3

    // 5. 类的成员重载函数
    auto f5 = std::bind(static_cast<char(C::*)(char)>(&C::class_overload_func), &c, _1);
    std::cout << f5('C') << '\n'; // C

    // 6. 虚函数
    auto f6 = std::bind(&C::virtual_func, _1, _2);
    C *d = new D();
    std::cout << f6(d, 0) << '\n'; // 1
    delete d;

    // 7. 函数模板
    auto f7 = std::bind(template_func<int>, 5);
    std::cout << f7() << '\n'; // 5

    // 8. 仿函数(函数对象)
    auto f8 = std::bind(std::less<int>(), 5, _1);
    std::cout << std::boolalpha << f8(4) << ' ' << f8(6) << std::noboolalpha << '\n'; // false true

    // 9. 指定返回类型的bind
    // 使本来返回int的函数通过类型转换返回char
    auto f9 = std::bind<char>(global_func, (int)'a', _1);
    std::cout << f9(3) << '\n'; // d
    return 0;
}

向std::bind传递引用

使用std::bind,我们可以指定参数或重新排列参数顺序,在传递参数时,std::bind传递给函数的是参数的拷贝(或移动),如果需要向函数传递引用,则需要std::ref和std::cref来完成。

#include<iostream>
#include<functional>
void f(int &a, int &b) {
    ++a, --b;
    std::cout << "in f: " << a << ' ' << b <<'\n';
}
int main()
{
    using namespace std::placeholders;
    int a = 5, b = 5;
    auto f1 = std::bind(f, a, _1);
    f1(b);
    std::cout << "in main: " << a << ' ' << b << '\n'; // 5 4
    f1(b);
    std::cout << "in main: " << a << ' ' << b << '\n'; // 5 3

    std::cout << '\n';
    a = 5, b = 5;
    auto f2 = std::bind(f, std::ref(a), _1);
    f2(b);
    std::cout << "in main: " << a << ' ' << b << '\n'; // 5 4
    f2(b);
    std::cout << "in main: " << a << ' ' << b << '\n'; // 5 3
    return 0;
}

上述代码输出:

in f: 6 4
in main: 5 4
in f: 7 3
in main: 5 3

in f: 6 4
in main: 6 4
in f: 7 3
in main: 7 3

在第一个例子中,std::bind将a的值拷贝到另一个对象(假设是x),并将这个对象传递给了f函数,在main函数中a的值没有变化,而在f函数调用过程中,通过引用传递到f中的x的值发生了变化,并在两次调用间保留了上次的变化。 而第二个例子采用std::ref将a通过引用传递参与bind,在main函数中发现a的值发生了正确的变化。

std::bind嵌套

在绑定一个函数的过程中,可以嵌套地将一个std::bind作为一个参数,例如

#include<iostream>
#include<functional>
int f(int a, int b) {
    return a + b;
}
int g(int a) {
    return a + 1;
}
int main()
{
    using namespace std::placeholders;
    auto f1 = std::bind(f, _1, std::bind(g, _1));
    std::cout << f1(3) << '\n'; // 7
    return 0;
}

其中,两个std::bind共享_1,调用f1(3)时,3将被传递到两个placeholder的位置。 当然,即使不嵌套,同一个placeholder也可以出现多次,只要最后有一个合理的结果即可。

以上就是std::bind的介绍,在C++11之前出现的很多包装函数的操作,都可以使用std::bind以优美的方式实现,当然,std::bind本身的实现不是那么容易理解的,由于博主认识有限,本文就暂且到这里吧。

0

2 个评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注