10.C++ bind 绑定器和 function 函数对象

本节分为四部分:

  1. C++ STL 中的绑定器
  2. function 示例和实现
  3. bind 和 function 实现线程池
  4. lambda 表达式

1. C++ STL 中的绑定器

1. 介绍绑定器

1
2
3
std::bind1st
// 和
std::bind2nd

以上两个函数用于将一个 二元函数对象 转换为 一元函数对象

bind 意思为:绑定,1st 代表 first;2nd 代表 second,说明如下:

1
2
3
4
5
template<typename F, typename T>
std::binder1st<F> bind1st(const F& f, const T& x);

template<typename F, typename T >
std::binder2nd<F> bind2nd( const F& f, const T& x )

将给定的参数 x 绑定到 二元函数对象 F 的第一个或第二个形参。也就是说,将 x 存储在该包装器中,如果调用该包装器,则将 x 作为 F 的第一个或第二个形参传递。

bind1st:相当于操作:x Operation value == operator() 的第一个形参变量绑定成一个确定的值。
bind2nd:相当于操作:Operation value x == operator() 的第二个形参变量绑定成一个确定的值。

image.png

2. 实例

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
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>
using namespace std;

// 创建函数
template<typename Container>
void showContainer(Container& con)
{
// 编译器是从上到下编译的,这个还没有实例化,它不知道这个名字作用域后面的iterator是类型还是变量
// typename告知编译器后面类型的作用域后面是类型
typename Container::iterator it = con.begin();
for (; it != con.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}

int main()
{
// 定义容器
vector<int> vec;
srand(time(nullptr));

// 遍历添加
int i = 0;
for (i = 0; i < 10; ++i)
{
vec.push_back(rand() % 100 + 1);//随机出来的数字,并不是有序的
}

// 初始化状态
cout << "Init:";
showContainer(vec);

// 从大到小排序
cout << "Sort:";
sort(vec.begin(), vec.end(), greater<int>());
showContainer(vec);

// 把 70 按照顺序插入到 vec 容器当中,找第一个小于 70 的数字
vector<int>::iterator it = find_if(
vec.begin(),
vec.end(),
bind1st(greater<int>(), 70)
);
// auto it1 = find_if(vec.begin(), vec.end(),bind2nd(less<int>(), 70));
// 判断
if (it != vec.end())
{
vec.insert(it, 70);
}
cout << "Insert:";
showContainer(vec);

return 0;
}

image.png

3. 实现原理

  1. find_if 源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// FUNCTION TEMPLATE find_if
_EXPORT_STD template <class _InIt, class _Pr>
_NODISCARD _CONSTEXPR20 _InIt find_if(
_InIt _First,
const _InIt _Last,
_Pr _Pred
)
{
// find first satisfying _Pred
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
for (; _UFirst != _ULast; ++_UFirst) {
if (_Pred(*_UFirst)) {
break;
}
}

_Seek_wrapped(_First, _UFirst);
return _First;
}

其中第三个参数 _Pred 是一个一元函数对象

  1. bind1st 源码:
1
2
3
4
5
6
7
8
9
10
11
// FUNCTION TEMPLATE bind1st
_EXPORT_STD template <class _Fn, class _Ty>
_NODISCARD binder1st<_Fn> bind1st(
const _Fn& _Func,
const _Ty& _Left
)
{
// return a binder1st functor adapter
typename _Fn::first_argument_type _Val(_Left);
return binder1st<_Fn>(_Func, _Val);
}

它是一个函数模板,返回一元函数对象 binder1st

  1. bind2nd 源码:
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
// CLASS TEMPLATE binder1st
template<class _Fn>
class binder1st
: public unary_function<
typename _Fn::second_argument_type,
typename _Fn::result_type>
{
// functor adapter _Func(stored, right)
public:
typedef unary_function<
typename _Fn::second_argument_type,
typename _Fn::result_type> _Base;
typedef typename _Base::argument_type argument_type;
typedef typename _Base::result_type result_type;

binder1st(
const _Fn& _Func,
const typename _Fn::first_argument_type& _Left)
: op(_Func)
, value(_Left)
{
// construct from functor and left operand
}

result_type operator()(const argument_type& _Right) const
{
// apply functor to operands
return (op(value, _Right));
}

result_type operator()(argument_type& _Right) const
{
// apply functor to operands
return (op(value, _Right));
}

protected:
// the functor to apply
_Fn op;
// the left operand
typename _Fn::first_argument_type value;
};

一元函数对象默认构造函数接受两个参数, opvalue。赋值运算符接收一个参数,并使用传入的 opvaule 进行运算:op(value, _Right)

4. 实现

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// bind1st 实现
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>
using namespace std;

// 创建函数
template<typename Container>
void showContainer(Container& con)
{
// 编译器是从上到下编译的,这个还没有实例化,它不知道这个名字作用域后面的iterator是类型还是变量
// typename告知编译器后面类型的作用域后面是类型
typename Container::iterator it = con.begin();
for (; it != con.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}

// 实现 find_if
template<typename Iterator, typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare comp)
{
// 遍历这2个迭代器之间的元素,如果满足函数对象的运算,就返回当前迭代器,如果都不满足,返回end()
for (; first != last; first++)
{
// comp.operator()(*first)一元函数对象,因为要从容器拿1个元素和它指定的元素比较
if (comp(*first))
{
// my_find_if 需要 一元函数对象,而在库里面都是二元的
return first;
}
}
return last;
}

// 实现 bind1st
// 绑定器是函数对象的一个应用
template<class Compare, class T>
class _mybind1st
{
public:
_mybind1st(Compare comp, T val)
: _comp(comp)
, _val(val)
{
// 列表初始化
}

// 函数对象
bool operator()(const T& second)
{
return _comp(_val, second);
}

private:
Compare _comp;
T _val;
};

// mybind1st(greater<int>(), 70)
template<typename Compare, typename T>
_mybind1st<Compare, T> mybind1st(Compare comp, const T& val)
{
// 直接使用函数模板,好处是,可以进行类型的推演
return _mybind1st<Compare, T>(comp, val);
}

int main()
{
vector<int> vec;
srand(time(nullptr));
for (int i = 0; i < 20; ++i)
{
vec.push_back(rand() % 100 + 1);
}
cout << "Init:";
showContainer(vec);

// greater 二元函数对象
// 大到小排序
sort(vec.begin(), vec.end(), greater<int>());
cout << "Sort:";
showContainer(vec);

/*
把70按顺序插入到vec容器当中 找第一个小于70的数字
operator()(const T &val)
greater a > b
less a < b
绑定器 + 二元函数对象 => 一元函数对象
bind1st: + greater bool operator()(70, const _Ty& _Right)
bind2nd: + less bool operator()(const _Ty& _Left, 70)
*/
vector<int>::iterator it1 = my_find_if(
vec.begin(),
vec.end(),
mybind1st(greater<int>(), 70)
);
// auto it1 = my_find_if(vec.begin(), vec.end(),bind2nd(less<int>(), 70));
if (it1 != vec.end())
{
vec.insert(it1, 70);
}
cout << "Insert:";
showContainer(vec);

return 0;
}

image.png

2. function 示例和实现

function 是一个函数包装器模板,最早来自 boost 库。在 c11 标准中将其纳入标准库。该函数包装器模板可以包装任何类型的可调用元素,例如普通函数和函数对象

function 最大的作用就是保留可调用元素的类型

解决绑定器,函数对象,lambda 表达式只能使用在一条语句中的问题;

定义:传递一个  _Fty  function type 希望传递一个函数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
// CLASS TEMPLATE function
template<class _Fty>
class function
: public _Get_function_impl<_Fty>::type
{
// wrapper for callable objects
private:
typedef typename _Get_function_impl<_Fty>::type _Mybase;

public:
function() noexcept
{
// construct empty function wrapper
}

// ...
}

1. function的应用

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
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>
#include <string>
using namespace std;

void hello1()
{
cout << "hello world1!" << endl;
}
// void (*pfunc)(string)
void hello2(const string& str)
{
cout << str << endl;
}

int sum(int a, int b)
{
cout << "Sum:";
return a + b;
}

class Test
{
public:
// 必须依赖一个对象 void (Test::*pfunc)(string)
void hello(string str) { cout << str << endl; }
};

int main()
{
/*
1.用函数类型实例化 function
2.通过 function 调用 operator() 函数的时候,需要根据函数类型传入相应的参数
*/

// 从 function 的类模板定义出,看到希望用一个函数类型实例化 function
function<void()> func1 = hello1;
// func1.operator()() => hello();
func1();

function<void(string)> func2 = hello2;
func2("hello world2!");

function<int(int, int)> func3 = sum;
sum(2, 3);

// operator() 表达式
function<int(int, int)> func4 =
[](int a, int b) ->int { return a + b; };
cout << "function::operator():" << func4(2, 5) << endl;

// 成员函数变量
function<void(Test*, string)> func5 = &Test::hello;
Test* t = new Test();
func5(t, "call Test::hello!");

return 0;
}
1
2
3
4
// 类成员函数  
// 报错:& 左值引用问题
function<void(Test*, string)> func5 = &Test::hello;
func5(&Test(), "call Test::hello!");//临时对象调用

原因:

其实是可以实现取地址的,对象是在站内构造的,可以通过栈偏移指向;但是编译器不支持,无法实现兼容。

报左值引用错误是因为仿函数要用,上面初始化的仿函数是个 this call,要提供 this,而 this 是结构体的指针,无法通过取地址实现,而且临时构造不能允许取地址。

test() 的声明周期就那一行,结束就被销毁了是个右值,只有左值能取址。

2. 模板的完全特例化和部分特例化

对于下面的 compare,对于 char 类型的比较不能满足实际的需要,因此对 compare 特例化一个版本 compare<const char *>

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
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>
#include <string>
using namespace std;

template<typename T>
bool compare(T a, T b)
{
cout << "template compare" << endl;
return a > b;
}

template<>
bool compare<const char*>(const char* a, const char* b)//特例化
{
cout << "compare<const char*>" << endl;
return strcmp(a, b) > 0;
}

int main()
{
compare(10, 20);//
compare("aaa", "bbb");//T const char*

return 0;
}

匹配原则:有完全特例化则先匹配完全特例化,其次部分特例化,最后函数模板

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
65
66
67
68
69
70
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>
#include <string>
using namespace std;

// 类模板
template<class T>
class Vector
{
public:
Vector() { cout << "call Vector template init" << endl; }
};

// 下面这个是对char*类型提供的完全特例化版本(<>中T已知:即下面的char *) #1
template<> // 特例化的语法
class Vector<char*>
{
public:
Vector() { cout << "call Vector<char*> init" << endl; }
};

// 下面这个是对指针类型提供的部分特例化版本 #2
// 仅知道是一个指针,类型需要提供
template<class Ty>
class Vector<Ty*>
{
public:
Vector() { cout << "call Vector<Ty*> init" << endl; }
};

// 指针函数指针(有返回值,有两个形参变量)提供的部分特例化
template<class R, class A1, class A2>
class Vector<R(*)(A1, A2)>
{
public:
Vector() { cout << "call Vector<R(*)(A1, A2)> init" << endl; }
};

// 针对函数(有一个返回值,有两个形参变量)类型提供的部分特例化
template<class R, class A1, class A2>
class Vector<R(A1, A2)>
{
public:
Vector() { cout << "call Vector<R(A1, A2)> init" << endl; }
};

int sum(int a, int b) { return a + b; }

int main()
{
Vector<int> vec1;
Vector<char*> vec2;
Vector<int*> vec3;
Vector<int(*)(int, int)> vec4;
Vector<int(int, int)> vec5;// function

// 注意区分一下函数类型和函数指针类型
typedef int(*PFUNC1)(int, int);
PFUNC1 pfunc1 = sum;
cout << pfunc1(10, 20) << endl;

typedef int PFUNC2(int, int);
PFUNC2* pfunc2 = sum;
cout << (*pfunc2)(10, 20) << endl;

return 0;
}

image.png

实参推演:

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
#include <iostream>
#include <typeinfo>
using namespace std;

int sum(int a, int b) { return a + b; }

// T 包含了所有的大的类型 返回值,所有形参的类型都取出来
template<typename T>
void func(T a)
{
cout << typeid(T).name() << endl;
}

//细化返回值类型和参数类型
template<typename R, typename A1, typename A2>
void func2(R(*a)(A1, A2))
{
cout << typeid(R).name() << endl;
cout << typeid(A1).name() << endl;
cout << typeid(A2).name() << endl;
}

class Test
{
public:
int sum(int a, int b) { return a + b; }
};

//实参推演每一个类型
template<typename R, typename T, typename A1, typename A2>
void func3(R(T::* a)(A1, A2))
{
cout << typeid(R).name() << endl;
cout << typeid(T).name() << endl;
cout << typeid(A1).name() << endl;
cout << typeid(A2).name() << endl;
}

int main()
{
cout << "---------------------func1\n";
func(10);
func("aaa");
func(sum);//T int (*)(int,int) int (int,int)
func(&Test::sum);//int (__thiscall Test::*)(int,int)

cout << "---------------------func2\n";
func2(sum);

cout << "---------------------func3\n";
func3(&Test::sum);// int (__thiscall Test::*)(int,int)

return 0;
}

image.png

3. function 原理解析

源码:

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
_EXPORT_STD template <class _Fty>
class function : public _Get_function_impl<_Fty>::type { // wrapper for callable objects
private:
using _Mybase = typename _Get_function_impl<_Fty>::type;

public:
function() noexcept {}

function(nullptr_t) noexcept {}

function(const function& _Right) {
this->_Reset_copy(_Right);
}

template <class _Fx, typename _Mybase::template _Enable_if_callable_t<_Fx, function> = 0>
function(_Fx&& _Func) {
this->_Reset(_STD forward<_Fx>(_Func));
}

function& operator=(const function& _Right) {
function(_Right).swap(*this);
return *this;
}

function(function&& _Right) noexcept {
this->_Reset_move(_STD move(_Right));
}
}

function 底层是一个可变参的偏特化函数对象

实现:

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
65
66
67
68
69
70
71
72
73
74
75
#include <iostream>
#include <typeinfo>
#include <string>
#include <functional>
using namespace std;

// function 函数对象类型的实现原理
void hello(string str) { cout << str << endl; }
int sum(int a, int b) { return a + b; }

// 定义函数模板
template<class Fty>
class myfunction{};

// 部分偏特化版本 <R(A1)>
template<class R, class A1>
class myfunction<R(A1)>
{
public:
using PFUNC = R(*)(A1);
myfunction(PFUNC pfunc) : _pfunc(pfunc) {}

R operator()(A1 arg)
{
// hello(arg);
return _pfunc(arg);
}

private:
PFUNC _pfunc;
};

/*
//部分偏特化版本 R(*)(A1, A2);
template<class R, class A1, class A2>
class myfunction<R(A1, A2)>
{
public:
using PFUNC = R(*)(A1, A2);
myfunction(PFUNC pfunc) :_pfunc(pfunc) {}
R operator()(A1 arg1, A2 arg2)
{
return _pfunc(arg1, arg2);//hello(arg)
}

private:
PFUNC _pfunc;
};
*/

//通用版本
template<typename R, typename... A>//一组可变参数个数:表示一组类型
class myfunction<R(A...)>
{
public:
using PFUNC = R(*)(A...);
myfunction(PFUNC pfunc) :_pfunc(pfunc) {}
R operator()(A... arg)//一组形参变量
{
return _pfunc(arg...);//hello(arg) 表示一组形参变量
}
private:
PFUNC _pfunc;
};

int main()
{
myfunction<void(string)> func1(hello);
func1("hello world!");// func1.operator()("hello world!")

myfunction<int(int, int)> func2(sum);
cout << func2(10, 20) << endl;

return 0;
}

3. bind 和 function 实现线程池

C++11 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
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <typeinfo>
#include <string>
#include <memory>
#include <vector>
#include <functional>
#include <thread>
using namespace std;
using namespace placeholders;

// C++ 11 bind 绑定器 -> 返回的结果是一个函数对象
void hello(string str) { cout << str << endl; }
int sum(int a, int b) { return a + b; }

class Test
{
public:
int sum(int a, int b) { return a + b; }
};

int main()
{
// bind 是函数模板,可以自动退盐模板类型参数
cout << "----- bind -----" << endl;
bind(hello, "hello bind 1!")();
cout << bind(sum, 10, 20)() << endl;
cout << bind(&Test::sum, Test(), 20, 30)() << endl;

cout << "----- placeholder -----" << endl;
// 参数占位符 placeholder 绑定器除了语句,无法继续使用
// 只是占位的作用,调用的时候就要传递参数了
// 书写的时候使用多少个占位符,就是意味着用户调用的时候要传入几个参数
bind(hello, placeholders::_1)("hello bind 2!");
cout << bind(sum, placeholders::_1, placeholders::_2)(200, 300) << endl;

// 此处把 bind 返回的绑定器 binder 复用
cout << "----- function -----" << endl;
function<void(string)> func1 = bind(hello, _1);
func1("hello function 1!");
func1("hello function 2!");
func1("hello function 3!");

return 0;
}

在语句 bind(hello, "hello bind!")(); 中,bind 将 “hello bind 1!” 绑定至 hello 的 string 类型参数,并返回一个函数对象,调用这个函数对象的operator() 函数,完成打印字符串的过程。

在语句 bind(hello, _1)("hello bind 2!"); 中的 _1 是名称空间 placeholders 中的,用法 placeholder::_1。此为参数占位符,代表 hello 的第一个参数等待用户输入。在本例中将参数 “hello bind 2!” 传递给 operator() 函数完成调用。

用 function 实现对 bind 绑定的函数对象的类型保留

bind 有个缺点:bind 无法保存它所绑定过的函数对象!

所以就需要 function 和它进行配合。

image.png

placeholders 占位符:最多支持20个

image.png

1. 线程池模拟

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <iostream>
#include <typeinfo>
#include <string>
#include <memory>
#include <vector>
#include <functional>
#include <thread>
using namespace std;
using namespace placeholders;

// 线程类
class Thread
{
public:
Thread(function<void(int)> func, int no)
: _func(func)
, _no(no) { }
// 这里需要包含头文件 #include<thread>
thread start()
{
// 定义线程 t 执行 func 函数
// _func(_no)
thread t(_func, _no);
return t;
}

private:
// 接收绑定器返回的函数对象
function<void(int)> _func;
// 线程池编号
int _no;
};

// 线程池类
class ThreadPool
{
public:
ThreadPool() {}
~ThreadPool()
{
// 这里是指针,所以不能依靠vector析构自动析构,得手动释放Thread对象占用的堆资源
for (int i = 0; i < _pool.size(); ++i)
{
delete _pool[i];
}
}
// 开启线程池
void startPool(int size)
{
// 遍历线程大小
for (int i = 0; i < size; ++i)
{
// 添加线程
_pool.push_back(
new Thread(bind(&ThreadPool::runInThread, this, _1), i)
);
}

// 执行线程函数
for (int i = 0; i < size; ++i)
{
// 添加线程池
_handler.push_back(_pool[i]->start());
}

for (thread& t : _handler)
{
// 等待线程执行完毕
t.join();
}
}

private:
vector<Thread*> _pool;
vector<thread> _handler;

// 把runInThread这个成员方法充当线程函数 thread pthread_create
void runInThread(int id)
{
cout << "call runInThread! id: " << id << endl;
}
};


// 使用
int main()
{
ThreadPool pool;
pool.startPool(10);

return 0;
}

4. lambda 表达式

lambda这个词起源于数学上的λ,在C++中利用lambda表达式,可以方便的定义和创建匿名函数。lambda可以看做函数对象的升级版。改进了函数对象以下的缺点:

  • 使用在泛型算法中的参数传递
  • 比较性质/自定义操作
  • 优先级队列
  • 智能指针

1. 表达式语法

image.png

1
[捕获外部变量](形参列表)->返回值{操作代码};

如果 lambda 不需要返回值,那么返回值可以省略。也就是这样:

1
[捕获外部变量](形参列表){操作代码};
  • 参数列表:与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
  • mutable默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略
  • -> 返回值类型:与普通函数返回值一样表达函数的返回值类型,可以省略,译器会对返回值类型进行推导。
  • 函数体:可以使用参数以及捕获到的变量。

捕获外部变量方式:

  • []:表示不捕获任何外部变量
  • [=]:捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
  • [&]:捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
  • [this]:捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。
  • [=,&a]:以传值的方式捕获外部的所有变量,但是a变量以传引用的方式捕获
  • [a,b]:以传值的方式捕获外部变量 a 和 b
  • [a,&b]:a 以传值方式捕获,b 以传引用的方式捕获
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

int main()
{
// 最简单的lamber表达式,没有任何意义
[] {};

// 省略参数列表和返回值类型,由编译器推导
int a = 3, b = 4;
[=] { return a + b; }; // 函数体被的分号不要忘

// 引用传递的方式可以改变变量的值
auto func1 = [&](int c) { return b = a + c; };
func1(10);

// 值传递捕获不能改变变量的值,除非使用 mutable
auto func2 = [=](int c) mutable { return b = a + c; };
func2(10);

return 0;
}

2. lambda 底层原理 - 函数对象

  • 无参无返回值的 lambda
1
2
auto func1 = [](){cout << "hello world!" << endl; };
func1();

其对应的类:

1
2
3
4
5
6
7
8
9
10
template<typename T = void>
class TestLambda01
{
public:
TestLambda01() {}
void operator()()const
{
cout << "hello world!" << endl;
}
};
  • 参数为整型返回值为整型的
1
2
auto func2 = [](int a, int b)->int { return a + b; };
cout << func2(20, 30) << endl;

其对应的类:

1
2
3
4
5
6
7
8
9
10
template<typename T = int>
class TestLambda02
{
public:
TestLambda02() {}
int operator()(int a, int b) const
{
return a + b;
}
};
  • 以引用方式获取参数的lambda
1
2
3
4
5
6
7
8
9
10
int a = 10;
int b = 20;
// “a”: 无法在非可变 lambda 中修改通过复制捕获
auto func3 = [&]()
{
int tmp = a;
a = b;
b = tmp;
};
func3();

其对应的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T = int>
class TestLambda03
{
public:
TestLambda03(int &a, int &b):ma(a), mb(b) {}
void operator()() const
{
int tmp = ma;
ma = mb;
mb = tmp;
}
private:
int &ma;
int &mb;
};
  • 以值方式获取参数的lambda:
1
2
3
4
5
6
7
auto func4 = [=]() mutable//并没有改变外面的a b ,传值
{
int tmp = a;
a = b;
b = tmp;
};
func4();

其对应的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T = int>
class TestLambda04
{
public:
TestLambda04(int a, int b) :ma(a), mb(b) { }
void operator()() const
{
int tmp = ma;
ma = mb;
mb = tmp;
}

private:
mutable int ma;
mutable int mb;
};

总结:

lambda 中的捕获参数列表对应类中成员变量的类型,返回值和参数列表对应 operator() 的返回值和参数列表。

image.png

lambda 表达式在编译期间被编译器自动转换成函数对象执行,调研 operator

3. lambda 表达式代替 switch

1
2
3
4
5
6
7
8
9
10
map<int, function<int(int, int)>> caculateMap;
caculateMap[1] = [](int a, int b)->int {return a + b; };
caculateMap[2] = [](int a, int b)->int {return a - b; };
caculateMap[3] = [](int a, int b)->int {return a * b; };
caculateMap[4] = [](int a, int b)->int {return a / b; };

cout << "选择:";
int choice;
cin >> choice;
cout << "10 op 15:" << caculateMap[choice](10, 15) << endl;

4. 按值捕获 & 捕获时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <string>
using namespace std;

int main() {
string str = "abc";

auto localStr = [=](string& s) { s = str; };

str = "hello world";

string str1;
localStr(str1);

cout << str;

return 0;
}

str = "abc" 当闭包生成的那一刻,被捕获的变量已经按值赋值的方式进行了捕获,后面那个str再怎么变化,已经和闭包对象里面的值没有关系了.

localStr 中 str 中的值在localStr 定义的时候就已经确定为abc了,不会再发生变化。

5. 按引用捕获 & 悬空引用

在 C++ 编程中,程序员有责任保证 Lambda 调用的时候,保证被捕获的变量仍然有效~!是的,责任在你,而不在编译器。如果不能很好理解这点,就会遇到悬空引用的问题!

悬空引用( dangling references ):就是说当创建了一个对象的引用类型的变量,但是被引用的对象被析构了、无效了。一般情况下,引用类型的变量必须在初始化的时候赋值,很少遇到这种情况,但是如果 lambda 被延迟调用,在调用时,已经脱离了当前的作用域,那么按引用捕获的对象就是悬空引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <string>
#include <functional>
using namespace std;

std::function<void(void)> func;

void test() {
std::string str = "abc";
func = [&]() { std::string a = str; cout << a; };
}

int main()
{
test();
func();

return 0;
}

image.png

发生 crash:func 引用的对象 str 在离开作用域 test() 后被析构了,在 main 函数中执行 func() 导致找到引用的对象 str,导致 crash。

6. lambda 实现指针自定义删除器

1
2
3
// 智能指针自定义删除器   delete p;  FILE*   fclose(FILE*)
unique_ptr<FILE, function<void(FILE*)>>
ptr1(fopen("data.txt", "w"), [](FILE *pf) {fclose(pf); });

7. lambda实现多种比较操作

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
#include <iostream>
#include <functional>
#include <vector>
#include <queue>
using namespace std;

class Data
{
public:
Data(int val1 = 10, int val2 = 10) :ma(val1), mb(val2) {}
bool operator>(const Data &data) const { return ma > data.ma; }
bool operator<(const Data &data) const { return ma < data.ma; }
int ma;
int mb;
};

int main()
{
// 优先级队列
//priority_queue<Data> queue;
using FUNC = function<bool(Data&, Data&)>;

priority_queue<
Data,
vector<Data>,
FUNC
> maxHeap([](Data& d1, Data& d2)->bool
{
return d1.mb > d2.mb;
});

maxHeap.push(Data(10, 20));
maxHeap.push(Data(15, 15));
maxHeap.push(Data(20, 10));
}

10.C++ bind 绑定器和 function 函数对象
http://example.com/2023/08/18/03.C++进阶部分/10.C++ bind 绑定器和 function 函数对象/
Author
Yakumo
Posted on
August 18, 2023
Licensed under