std::bind(二):包装成员函数

前言

关于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
37
void 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
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class CTest
{
public:
CTest() {}
~CTest() {}
public:
void func1(int n1, int n2)
{
cout << "func1 " << n1 << ' ' << n2 << endl;
}

int n_public;

private:
void func2(int n1, int n2)
{
cout << "func2 " << n1 << ' ' << n2 << endl;
}

int n_private;
};


void test2_1()
{
CTest testObj;
auto f2 = std::bind(&CTest::func1, testObj, 101, placeholders::_1);
f2(1); // same as call testObj.func1(101, 1)
}

void test2_2()
{
CTest testObj;
auto f2 = std::bind(&CTest::func1, &testObj, 101, placeholders::_1);
f2(2); // same as call testObj.func1(101, 2)
}

void test2_3()
{
CTest testObj;
CTest& obj = testObj;
auto f2 = std::bind(&CTest::func1, obj, 101, placeholders::_1);
f2(3); // same as call testObj.func1(101, 3)
}


void test2_4()
{
CTest testObj;
auto f2 = std::bind(&CTest::func1, placeholders::_1, placeholders::_2, 101);
f2(testObj, 4); // same as call testObj.func1(4, 101)
}

void test2_5()
{
CTest testObj;
// auto f2 = std::bind(&CTest::func2, &testObj, 101, placeholders::_1);
// 编译错误,func2不可访问
}

//func1 101 1
//func1 101 2
//func1 101 3
//func1 4 101

前三个函数tes2_1()tes2_2()tes2_3()的作用基本一致,就是将一个类的非静态成员函数和对象绑定,并且可以动态绑定一些参数,三种调用方式都可以,暂时没有发现什么问题,大家知道区别的可以指导我一下,我补充上来,需要注意的是函数std::bind()参数个数需要在原函数参数个数的基础上加两个,第一个很明显就是函数名,而第二个必须是调用这个函数的对象,至于传递的是指针还是引用都没有什么问题,这两个参数过后才是真正的原函数的参数。

函数test2_4()相对于前三个来说更加灵活,将对象也最为参数在调用时传入,这就相当于把一个成员函数看成,一个普通函数然后在第一个参数前加this指针的形式,后面这种调用方式在查看C++调用堆栈时应该很容易看到,本质上是一样,其实这里还有一个对象传递的问题,我们在成员变量时再测试一下。

函数test2_5()出现了编译错误,原因是在使用函数std::bind()的时候也要考虑到原函数的访问权限,在测试函数中访问对象的私有函数显然是不可以的。

成员变量的包装

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
void test3_1()
{
CTest testObj;
auto f3 = std::bind(&CTest::n_public, testObj);
f3(1) = 10;
cout << f3(1) << endl;
cout << testObj.n_public << endl;
}


void test3_2()
{
CTest testObj;
auto f4 = std::bind(&CTest::n_public, placeholders::_1);
f4(testObj) = 4;
cout << f4(testObj) << endl;
cout << testObj.n_public << endl;
}


void test3_3()
{
CTest testObj;
auto f3 = std::bind(&CTest::n_public, std::ref(testObj));
f3(1) = 11;
cout << f3(1) << endl;
cout << testObj.n_public << endl;
}

//10
//-858993460
//4
//4
//11
//11

这个成员变量的绑定测试结果,有没有让人意想不到呢?或者说这种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()包装的原对象,可以通过绑定后的函数修改。

总结

  1. std::bind()函数可以嵌套绑定
  2. std::bind()函数绑定成员函数时,函数名参数后面需要紧跟着类的对象作为参数
  3. std::bind()不仅可以绑定普通函数、成员函数、还可以绑定成员变量

完整代码

代码传送门

Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客