前言
关于std::bind()
对普通函数的包装作用,在之前的总结文章《std::bind(一):包装普通函数》已经举例说明过了,后来发现丢下了普通函数嵌套包装的情况,所以在这篇文章中继续说明一下,然后重点总结std::bind()
函数对成员函数的包装,在面向对象的大潮还未褪去的今天,还是成员函数见到的更多一些,所以讲讲对它的包装。
普通函数嵌套包装
实际上就是普通函数包装的变形和组合,直接写个例子吧,如果test1_1()
、test1_2()
、test1_3()
三个函数的输出结果都答对了就说明已经掌握了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37void func(int n1, int n2, int n3)
{
cout << n1 << ' ' << n2 << ' ' << n3 << endl;
}
int calc_value(int c1)
{
return c1 * c1;
}
void calc_value2(int c1)
{
int result = c1 * c1;
}
void test1_1()
{
auto f1 = std::bind(func, placeholders::_1, 101, std::bind(calc_value, placeholders::_2));
f1(11, 2); // same as call func(11, 101, calc_value(2))
}
void test1_2()
{
int n = 2;
auto f1 = std::bind(func, placeholders::_1, 101, std::bind(calc_value, std::ref(n)));
n = 4;
f1(11, 2); // same as call func(11, 101, calc_value(44)) 多出的参数2无人使用
}
void test1_3()
{
auto f1 = std::bind(func, placeholders::_1, 101, std::bind(calc_value2, placeholders::_2));
//f1(11, 2); // 编译出错,无法将参数 3 从“void”转换为“int”
}
// 11 101 4
// 11 101 16
第一个test1_1
函数的逻辑应该很容易理解,就是把函数calc_value(2)的返回值作为函数func
的第三个参数,而函数test1_2
中利用了std::ref()
传递引用的功能,将变量n
作为引用变量进行传递,在包装调用之前可以感知到参数n的变化。
其实难点在第三个函数test1_3
,可能大家知道这里会报错,因为我们需要返回值但是却包装了一个没有返回值的函数,但其实把第二行注释掉之后,程序就可以成功编译,也就是说包装错误的函数如果不被调用,是不会报错的,这一点和模板函类不使用就不会创建很相似,最终是相同的。
包装类成员
在深入学习std::bind()
这个函数之前一直以为它只能用来包装函数,后来通过进一步了解发现它还能用来包装成员变量,我们一起来看一下简单的实现方法。
成员函数的包装
这里我们不考虑静态成员函数,因为静态函数没有this
指针,和普通的函数基本一样,在用法上也没有很大的差异,所以此处的包装只考虑成员非静态函数,可以尝试分析以下几个例子。
1 | class CTest |
前三个函数tes2_1()
、tes2_2()
、tes2_3()
的作用基本一致,就是将一个类的非静态成员函数和对象绑定,并且可以动态绑定一些参数,三种调用方式都可以,暂时没有发现什么问题,大家知道区别的可以指导我一下,我补充上来,需要注意的是函数std::bind()
参数个数需要在原函数参数个数的基础上加两个,第一个很明显就是函数名,而第二个必须是调用这个函数的对象,至于传递的是指针还是引用都没有什么问题,这两个参数过后才是真正的原函数的参数。
函数test2_4()
相对于前三个来说更加灵活,将对象也最为参数在调用时传入,这就相当于把一个成员函数看成,一个普通函数然后在第一个参数前加this指针的形式,后面这种调用方式在查看C++调用堆栈时应该很容易看到,本质上是一样,其实这里还有一个对象传递的问题,我们在成员变量时再测试一下。
函数test2_5()
出现了编译错误,原因是在使用函数std::bind()
的时候也要考虑到原函数的访问权限,在测试函数中访问对象的私有函数显然是不可以的。
成员变量的包装
1 | void test3_1() |
这个成员变量的绑定测试结果,有没有让人意想不到呢?或者说这种f3(1) = 10;
写法已经让人很惊讶了,其实我在写例子的时候就是简单试试,没想到这样写居然可以,看起来好像把一个值赋值给了一个函数一样。
函数test3_1()
的第二个输出可能有点想不到,但是看到结果是有些人可能就明白了,因为在上一篇里提到“std::bind()
函数中的参数在被复制或者移动时绝不会以引用的方式传递,除非你使用了std::ref()
或者std::cref()
包装的参数”。
因为没有使用std::ref()
函数包装,所以std::bind()
函数绑定的testObj
对象实际上是原对象的副本,那么针对于副本的操作和修改自然就不会反应到原对象上,这也就是打印testObj.n_public
会输出随机值的原因。
函数test3_2()
在绑定时并没有具体到特定的对象,而是使用了placeholders::_1
占位符,这样生成的函数,在调用的时候再传入操作对象,那么此时修改对象属性就可以起作用了。
函数test3_3()
是针对于函数test3_1()
的,添加了std::cref()
包装的原对象,可以通过绑定后的函数修改。
总结
std::bind()
函数可以嵌套绑定std::bind()
函数绑定成员函数时,函数名参数后面需要紧跟着类的对象作为参数std::bind()
不仅可以绑定普通函数、成员函数、还可以绑定成员变量