可调用对象之 bind

  1. 1. 可调用对象
  2. 2. bind
    1. 2.1. 使用方法
    2. 2.2. bind的问题
  3. 3. bind_front与bind_back
  4. 4. 其他

1. 可调用对象

  很多算法可以通过可调用对象来提供自定义计算逻辑细节的能力,可调用对象可以是函数指针、函数对象(类)、bind、lambda表达式等。

2. bind

  std::bind通过绑定的方式,生成一个调用包装器(call wrapper),将可调用对象与部分参数绑定,形成一个新的可调用对象。

2.1. 使用方法

1. 使用案例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
bool MyCompare(int x, int y)
{
    return x > y;
}


int main()
{
    using namespace std::placeholders;     // 占位符定义在这里

    auto x = std::bind(MyCompare, _1, 3); 
    x(2);    // MyCompare(2, 3)
    x(6);    // MyCompare(6, 3)

    auto y = std::bind(MyCompare, _2, 3);
    y("hello", 7);   // MyCompare(7, 3)
}

  代码解析:

1
2
3
4
5
6
7
auto x = std::bind(MyCompare, _1, 3);
//   将3绑定到MyCompare的参数y, “_1”是参数x的占位符。
//   返回一个新的可调用对象f。  
//   但是要注意,占位符“_1”中的1指的是: “调用f()时,将f()中的第几个参数传给到MyCompare的参数x”,
//   请看如下代码:  
auto y = std::bind(MyCompare, _2, 3);
y("hello", 7);   // MyCompare(7, 3), 将f("hello", 7)中的第二个参数传给到MyCompare的参数x。

  我们来看一下bind的声明:

1
2
3
4
5
6
7
8
// (since C++11) (constexpr since C++20)

template< class F, class... Args > 
    bind( F&& f, Args&&... args );


template< class R, class F, class... Args >
    bind( F&& f, Args&&... args );

  在创建可调用对象时,也就是在调用std::bind时,参数列表Args&&中的参数顺序(包括占位符)对应着函数f的形参顺序,不在于占位符是“_1“还是”_2”。 而当我们调用可调用对象时,传给可调用对象几个参数,第一个传给“_1”, 第二个传给“_2”,以此类推。
  也就是说,当我们调用可调用对象时,传入的参数中,有些参数是无效的,因为实际使用的是绑定的参数。调用可调用对象时,参数的使用和原函数是一样的,至于那个参数会被采用,这是在调用std::bind时,通过Args&&中的参数顺序以及占位符设计好的。

2. 使用案例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
bool MyCompare(int x, int y)
{
    return x > y;
}

bool MyAnd(bool x, bool y)
{
    return x && y;
}


int main()
{
    using namespace std::placeholders;

    auto x = std::bind(MyCompare, _1, 3); 
    auto y = std::bind(MyCompare, 6, _1);
    auto z = std::bind(MyAnd, x, y);
    z(4);    // MyAnd(x(4, 3), y(6 ,4)) 
}

3. 使用案例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
bool MyCompare(int x, int y)
{
    return x > y;
}

int main()
{
    using namespace std::placeholders;
    auto x = std::bind(MyCompare, _1, _1);  // x 始终返回false
    x(6); 
}

2.2. bind的问题

  调用std::bind时,传入的参数会被复制,这可能导致一些风险。

1. 使用案例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void MyFunc(int* x)
{

}

auto fun()
{
    int x;
    return std::bind(MyFunc, &x);  // 在fun()返回时,x会被销毁
}

int main()
{
    auto f = fun();  
    // 当调用f时,绑定的参数已经失效了,甚至会导致内存错误。   
}

  若果有此类场景,可以使用智能指针来解决,在使用std::bind时,传入的参数应该是智能指针,保证参数有效。

2. 使用案例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void MyFunc(int& x)
{
    ++x;
}


int main()
{
    int x = 0;
    auto fun = std::bind(MyFunc, x);
    fun();
    std::cout << x << std::endl;
    // 输出为0, 因为std::bind,对于传入的参数采用了拷贝的方法, 压根不会改变x的值。
}

  这种情况需要使用std::ref、std::cref来改变参数的绑定方式:

1
auto fun = std::bind(MyFunc, std::ref(x));   

3. bind_front与bind_back

  std::bind_front, std::bind_back是C++20引入的新特性,用来给第一个参数绑定一个值,或者给最后一个参数绑定一个值。

4. 其他

  在使用bind时,绑定那些参数,调用可调用对象时,那些参数有效,参数是如何对应的,非常的绕,所以后来引入了lambda表达式。

comments powered by Disqus