I learned something today: C++ (part I)

Learn . Modern C++

​ 最近在读一本名叫“C++ High Performance”的书,觉得有一定帮助,学习了一点现代的C++,故记录。

Automatic type deduction: the auto keyword

In function signatures

​ 可以使用auto来标识函数返回值类型,例如以下表达式:

1
2
3
4
5
6
7
8
// Return type: value
int func() const // 无聊的传统用法
auto func() const // 炫酷的船新用法,结果与下面两个表达式相同
auto func() const -> int // 另一炫酷的船新用法, 当声明和定义不在一个文件时适用
// Return type: reference
auto& cref() const // 结果与下面两个表达式相同
const int& cref() const
auto cref() -> const int&

In variables

​ 在声明变量时使用auto,会令人产生一种正在使用某些动态类型语言的错觉,例如:

1
2
3
4
auto i = 0; 
auto x = Foo{};
auto y = func();
auto z = std::mutex{};

​ 使用了auto关键字后,我们可以不用担心自己是否使用了正确的变量类型,但我们仍需决定,我们想要的是reference 还是copy,以及变量是只读还是需要修改,如果是只读,最好显式的表明,例如使用 const auto&

The lambda function

Basic syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using namespace std;
int main(){
auto compare = [](int a,int b){
return a<b;
};// the lambda function, 接收a,b两个参数比较大小
cout<<compare(1,2)<<endl; //output 1
auto nums = vector<int>{6,4,3,9};
sort(nums.begin(), nums.end(), compare);
//Or: sort(nums.begin(), nums.end(), [](int a,int b){return a<b;});
for(auto num : nums)
cout<<num<<endl;
return 0;
}
/*输出:
1
3
4
6
9
*/

The capture block

​ Lambda function 的小括号里是参数,花括号里是函数语句,这些和一般的函数是一样的,多出来的方括号为capture block

1
2
3
4
auto count_above(const std::vector<int>& vals, int th) {  
auto is_above = [th](int v) { return v > th; };
return std::count_if(vals.begin(), vals.end(), is_above);
}

​ 以上代码中,参数th被capture block “捕获”,复制进了lambda function 内,从而可以灵活的改变is_above函数要比较的值。当然capture block 中也可以放reference。

1
2
3
4
5
6
7
int t = 0;
auto func = [t](int b){
cout << b+t << endl;
};
++t;
func(2);
//output: 3

​ 上面的lambda 与下面定义的这个类相似。但是每一个Lambda 的类型都是不同的,即使他们一毛一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
int t = 0;
class Func{
public:
Func(int t):t{t}{}
//with only one member function
auto operator()(int b)const{ cout << b+t << endl; }
private:
int t;
};
auto func = Func(t);
++t;
func(2);
//output: 3

​ 我们也可以在capture block里的初始化变量,可以看做在类里初始化成员变量一般。但是我们不能在普通的lambda 里更改capture block 所capture 的变量,原因可以在上面看到:对应类的唯一一个成员函数是用const标记的。要使得我们能够更改capture的变量,需要加上mutable这一modifier,或是一开始就传递引用。

1
2
3
4
5
6
7
8
9
auto func = [&t](int b){
t++;
cout << b+t << endl;
};
//or
auto func = [t](int b) mutable {
t++; //没有mutable会报错
cout << b+t << endl;
};

​ 通过使用[=] or [&],我们可以无脑的capture当前可用的所有变量,[=]意味着“captures all by value”,而[&]意味着“captures all by reference”。当然,我们并不是真正的把所有变量都被复制到了lambda内,只有我们实际用了的变量会真正地被复制。我们也可以指定个别变量的捕捉方式,如下

1
2
3
int a, b, c; 
auto func = [=, &c](){}; //Capture c by reference,and a,b by value
auto func = [&, c](){}; //Capture c by value,and a,b by reference

Lambdas and std::function

1
2
3
std::function< return_type ( parameter0, parameter1...) > 
// a std::function returning a bool and having two int as parameters:
auto func = std::function<bool(int,int)>{};

​ 如果我们的lambda有和某个std::function相同的返回类型且接收相同的参数类型,那么该lambda就可以装入该std::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
using namespace std;

class Killer
{
public:
Killer(function<void(void)> a): action_{a}{}

void takeAction() const
{
action_();
}

private:
function<void(void)> action_{};
};

int main()
{
auto killer1 = Killer([]() { cout << "Slash!" << endl; });
auto killer2 = Killer([]() { cout << "Shoot!" << endl; });
auto killer3 = Killer([]() { cout << "Hug!" << endl; });
auto killer_squad = vector<Killer>{killer1, killer2, killer3};
for (const auto& killer : killer_squad)
killer.takeAction();
return 0;
}
/*Output:
Slash!
Shoot!
Hug!
*/

​ 然而,相比直接用lambda,使用std::function付出的代价更大, 无论是从时间上还是空间上考虑。