C/C++编程:bind参数绑定

为什么需要参数绑定

引入lambda表达式的原因:

  • 像算法传递谓词时,由于一般的函数无法传递参数以外的信息,函数对象又比较麻烦,所以引入了lambda表达式

但是lambda表达式也有缺点:

  • 在类似功能多次使用的时候,每次定义lambda表达式也会比较麻烦

因此,C++11标准库又引入了新特性:参数绑定bind

怎么用

// 功能: 
bool istarget(const string& s){
    return s.size() < 2;
}
vector<string> v{"This","is", "a", "predicate", "."};
auto found = find_if(v.begin(), v.end(), istarget);
cout << *found << endl;

上面代码可以可以找到第一个长度小于2的string ,但是如果我们希望在istarget中选择string时使用变量而不是固定的2的时候,应该怎么做呢?

根据需求定义比较函数

在本例中,就是定义一个接受选择对象string对象和最小长度参数的istarget函数:

bool istarget(const string& s, int sz){
    return s.size() < sz;
}

使用参数绑定新的可调用对象

C++11标准库提供了一个bind函数,按照C++11 Primer的说法,可以将bind函数看着一个通用的函数适配器,它接受一个新的可调用对象来“适应”原对象的参数列表。调用bind的一半形式为:

auto newCallable = bind(callable, arg_list);

也就是说:

auto isTarget = bind(istarget, std::placeholders::_1, 2);

使用定义的可调用对象

auto found = find_if(v.begin(), v.end(), isTarget);
    cout << *found << endl;

istarget函数定义一次之后,可以使用bind函数适应各种算法的要求,从而实现了实现一次定义,多次使用的目标

如此,可调用对象就算凑齐了:函数,函数对象,lambada表达式,参数绑定。

接下来我们看看标准库中怎么说。

bind

头文件:

// STL
#include <functional>

// boost
#include <boost/bind.hpp>
using namespace boost;
  • std::bind的基本形式如下:

在这里插入图片描述

  • boost::bind的基本形式如下:
template<class R, class F>           bind(F, f);
template<class R, class F, class A1> bind(F f, A1 a1);

namespace{                           //匿名命名空间
	boost::arg<1> _1;
	boost::arg<2> _2; 
	...                             // 其他6个占位符定义
}

函数模板bind生成f的转发调用包装器。调用此包装器等价于绑定到 args 的参数调用 f

例如:

  • 如果有一个函数func,它的形式时:
func(a1, a2);
  • 那么,它将等价于一个具有无参operator()的bind函数对象调用:
bind(func, a1, a2)();
  • 这是bind最简单的形式。bind表达式存储了func和a1、a2的拷贝,产生了一个临时函数对象。因为func接受两个参数,而a1和a2都是实参,因此临时函数对象将具有一个无参的operator()。当operator()调用发生时函数对象把a1、a2的拷贝传递给func,完成真正的函数调用。

参数:

  • f - 可调用对象,包括函数指针、函数引用、成员函数指针、函数对象和lambda表达式等
  • args - 要绑定的参数列表
    • 参数的数量必须和f的参数数量相等,这些参数将会被传递给f作为入参
      • boost最多可以接受9个参数
      • STL支持绑定任意数量的参数
    • 可以被placeholders 的占位符 _1, _2, _3… 所替换,占位符可以取代bind中参数的位置,在发生函数调用时才接收真正的参数。
      • boost中的占位符位于匿名空间内,它们分别被定义为_1、_2、_3一直到_9
      • STL中的占位符位于std::placeholders命名空间内
      • 因此,STL中的占位符必须std::placeholders::_1,而boost中的占位符可以直接_1

返回值:

  • 返回一个函数对象
    • 这个函数对象内部保存了f的拷贝,具有operator(),返回值类型被自动推导为f的返回值类型
    • 这个函数对象满足 std::is_bind_expression<T>::value == true
    • 在发生调用时,这个函数对象将会把之前存储的参数转发给f完成调用

参数arg与占位符

  • 占位符怎么理解呢?就是先把位置占好,然后将来会给这个位置传参
  • 占位符的名字表示它在调用式中的顺序,而在绑定表达式中没有顺序的要求,_1不一定必须第一个出现,也不一定只出现一次,比如:
bind(func, _2, _1)(a1, a2);

返回一个具有两个参数的函数对象:
	- 第一个参数将放在函数func的第二个位置
	- 第二个参数则放在第一个位置
调用时等价于:

func(a2 , a1);
  • 我们必须在绑定表达式中提供函数要求的所有参数,无论是真实参数还是占位符均可以。但是所有的参数必须和f要求的参数一致
int f(int a, int b){  // 二元函数
	return a + b;
}

int g(int a, int b, int c){   //三元函数
	return a + b + c;
}


binf(f, _1, 9)(x);          // f(x, 9);  即9这个参数是固定的,x这个参数是不固定的
bind(f, _1, _2)(x, y);      // f(x, y);
bind(f, _2, _1)(x, y);      // f(y, x);
bind(f, _1, _1)(x, y);      // f(x, x);  y参数被忽略
bind(g, _1, 9, _2)(x, y);   // g(x, 8, y);
bind(g, _3, _2, _2)(x, y, z); // g(z, y, y);

为占位符更名

类似_1、_2、_3这样的名字,觉得不好怎么办?可以为它起别名

	// ------起别名-----
    auto & _a = _1;
    decltype(_2) & _b = _2;


	//---------使用----

ref引用传参

  • bind采用拷贝的方式存储对象和参数,如果参数很大或者无法拷贝,怎么办呢?可以使用ref库
    在这里插入图片描述
#include <random>
#include <iostream>
#include <memory>
#include <functional>

using namespace std;

void f(int n1, int n2, int n3, const int& n4, int n5)
{
    std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}
int main() {
    // 演示参数重排序和按引用传递
    int n = 7;
    auto f1 = std::bind(f, std::placeholders::_2, 42, std::placeholders::_1, std::cref(n), n);  // f1 =  f(_1, 42, _2, &n, 7)
    n = 10;
    f1(1, 2, 1001); // 1 为 _1 所绑定, 2 为 _2 所绑定,不使用 1001  ====> f(2, 42, 1, 10, 7) 的调用

    return 0;
}

bind嵌套使用

bind可以嵌套使用,一个bind表达式生成的函数对象可以被另一个bind再绑定,从而实现f(g(x))这样的效果
在这里插入图片描述

#include <random>
#include <iostream>
#include <memory>
#include <functional>

using namespace std;
using namespace std::placeholders;  // 对于 _1, _2, _3...

void f(int n1, int n2, int n3, const int& n4, int n5)
{
    std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}

int g(int n1)
{
    return n1;
}
int main() {
    

    auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
    f2(10, 11, 12); // 进行到 f(12, g(12), 12, 4, 5); 的调用

    return 0;
}

在这里插入图片描述

lambda表达式 VS bind

lambda表达式可以就地声明匿名函数,可以替代bind,其捕获列表[…]相当于绑定的变量,函数参数列表(…)相当于bind的占位符

在这里插入图片描述

使用

绑定普通函数/函数指针

int f(int a, int b){  // 二元函数
	return a + b;
}

int g(int a, int b, int c){   //三元函数
	return a + b + c;
}


// ------------------------------------
// ------------绑定普通函数--------------
// ------------------------------------
std::cout << bind(f, 1, 2) << "<===>"<< f(1, 2) <<  "\n";  // 输出3
std::cout << bind(g, 1, 2, 3)  << "<===>"<< g(1, 2, 3) <<"\n"; // 输出7


// ------------------------------------
// ------------函数指针--------------
// ------------------------------------
typedef decltype(&f) f_type;   //函数指针定义
typedef decltype(&g) g_type;   //函数指针定义

f_type pf = f;
g_type pg = g;

// -----------绑定函数指针---------------------------
int x, y, z;
std::cout << bind(pf, _1, 9)(x) << "\n";            // (*pf)(x, 9);
std::cout << bind(pg, _3, _2, _2)(x, y, z) << "\n"; // (*pg)(z, y, y);

绑定重载函数

直接使用函数名的绑定方式存在一定限制,比如重载函数时bind无法确定具体要绑定哪一个,就会出现编译错误

在这里插入图片描述
怎么办呢?可以用typedef定义函数指针类型,在使用函数指针变量来绑定

在这里插入图片描述但是这个方法不适合模板函数,因为我们很难写出一个准确的目标函数指针类型。怎么办呢?只能用lambda来解决

在这里插入图片描述

在这里插入图片描述

绑定变参函数

如果bind无法自动推导出返回值类型,这时我们必须显示指出返回值类型,比如要绑定print()

在这里插入图片描述

绑定成员函数

理论

bind也可以绑定类的成员函数。

X x
bind(&X::func, x, _1, _2, ...);
  • 类的成员函数不同于普通函数,因为成员函数指针不能直接调用operator(),它必须被绑定到一个对象或者指针,然后才能得到this指针进而调用成员函数。
  • 因此bind第一个参数必须是类的实例、引用或者指针

bind同样支持绑定虚成员函数,用法与非虚函数相同,虚函数的行为将由实际调用发生时的实例来决定。

举例

  • 第一个例子:
struct demo{
	int f(int a, int b){
		return a + b;
	}
};


// ------------------------------------
// ------------绑定成员函数--------------
// ------------------------------------
    demo a, &rf = a;    //类的实例对象和引用
    demo *p = &a;        //指针


// 我们必须在成员函数前加上取地址操作符&,表示这是一个成员函数指针,否则无法通过编译。
    cout << bind(&demo::f, a, _1, 20)(10) << endl;
    cout << bind(&demo::f, rf, _2, _1)(10, 20) << endl;
    cout << bind(&demo::f, p, _1, _2)(10, 20) << endl;
  • 第二个例子:
    • bind能够绑定成员函数,这是个非常有用的功能,它可以替代标准库中令人迷惑的mem_func和mem_func_ref绑定器,用来配合标准算法中操作容器的对象。
    • 下面的代码使用bind搭配标准算法for_each用来调试容器中所有对象的print()函数:
struct point{
	int x, y;
	point(int a = 0, int b = 0) : x(a), y(b){}
	void print(){
		cout << "(" << x << " , " << y <<")\n";
	}
};


vector<point> v(10);
for_each(v.begin, v.end(), bind(&point::print, _1));

绑定函数对象

  • 如果函数对象有内部定义的result_type,那么bind可以自动推导出所有的返回值类型,用法和普通函数一样
  • 如果函数对象没有内部定义的result_type,那么需要在绑定形式上做一点改动,,比如:bind<result_type>(functor, ...)

举个例子

  • 内部的
    bind(greater<int>(), _1, 10); //====》 x > 10
    bind(plus<int >(), _1, _2);       //  ==== 》x + y
  • 绑定自定义函数对象
// 自定义函数对象
struct func{
    int operator()(int a, int b){
        return a + b;
    }
};
int main()
{
    auto f = bind(func(), _1, 10);
    cout << f(11) << "\n";

    cout << bind(func(), _1, 10)(11) <<"\n";
    cout << bind<int>(func(), _1, 10)(11);  //用模板参数指明返回类型
    return 0;
}

绑定成员变量

bind可以绑定类的public成员变量,用法和绑定成员函数类似。

选择处pair对象的first和second成员

在这里插入图片描述
在这里插入图片描述

自定义类


struct Foo {
    Foo() = default;
    Foo(int d) : data_(d) {

    }

    int data_ = 10;
};


int main()
{
    Foo foo;
    auto f4 = bind(&Foo::data_, _1);
    cout << f4(foo) << '\n';

    vector<Foo> fooVec{11, 12, 13, 14, 15, 15};

    vector<int> intVec(fooVec.size());
    /*
     * bind(&Foo::data_, _1):取出Foo对象的data_
     * transform调用bind表达式操作容器fooVec,将它的成员变量逐个填入intVec中
     * */
    transform(fooVec.begin(), fooVec.end(), intVec.begin(), bind(&Foo::data_, _1));

    for(auto x : intVec){
        cout << x <<",";
    }
    return 0;
}

#include <random>
#include <iostream>
#include <memory>
#include <functional>

using namespace std;

int main() {
    // 常见使用情况:以分布绑定 RNG
    std::default_random_engine e;
    std::uniform_int_distribution<> d(0, 10);
    std::function<int()> rnd = std::bind(d, e); // e 的一个副本存储于 rnd
    for(int n=0; n<10; ++n)
        std::cout << rnd() << ' ';
    std::cout << '\n';

    return 0;
}

第1、2、3… 调用都是相同的
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值