C++11拓展语法
- 统一的列表初始化
- 内置类型初始化
- 自定义类型
- initializer_list初始化
- 具体应用
- 声明
- auto
- decltype
- nullptr
- 范围for
- 智能指针
- STL新增容器
- 右值引用
- 左值和右值
- 左值引用和右值引用
- 移动构造和移动赋值
- 左右值引用使用场景
- 完美转发
- lambda表达式
- 使用格式
- 应用
- 捕捉列表
- 类的新功能
- 可变参数模板
- 容器的emplace接口
- 包装器
- function包装器
- bind包装器
- 线程库
C++11是继C++03之后的一次大更新,出现了很多不同的语法,这里来逐一学习一下吧。
统一的列表初始化
C++11实现了初始化的大一统,无论是内置类型还是自定义类型都可以通过{}来进行初始化。
内置类型初始化
先来观测下列代码:
int main()
{int x = { 10 };//列表初始化int y{ 15 };//忽略‘=’的列表初始化char ch{ 'c' };cout << x << endl << y << endl << ch << endl;return 0;
}
运行结果:
10
15
c
上述结果表明,对于内置类型可以以= { 参数 } 或 { 参数 }的形式进行初始化。
自定义类型
先以STL自带的容器进行试验:
int main()
{string str1{ "Love" };cout << str1 << endl;return 0;
}
输出结果为:
Love
实际上本质是参数的隐式类型转换,不相信的话我们来以简单的自定义类型试验一下。
先定义一个简单的Point类型:
class Point
{
public:Point(const int x,const int y):_x(x),_y(y){cout << "构造" << endl;}Point(const int x):_x(x), _y(x){cout << "构造" << endl;}void Show(){cout << _x << ' ' << _y << endl;}private:int _x;int _y;
};int main()
{Point x{ 1,2 };x.Show();Point y={ 11,2 };y.Show();Point z = 1;z.Show();}
运行结果为:
构造
1 2
构造
11 2
构造
1 1
可以看到是调用了对应的构造函数来隐式类型转换,然后拷贝给Point。
如果像下面这种:
explicit Point(const int x,const int y):_x(x),_y(y)
{cout << "构造" << endl;
}
error C3445: "Point" 的复制列表初始化不能使用显式构造函数
或者
Point& w = 2;
error C2440: “初始化”: 无法从“int”转换为“Point &”
initializer_list初始化
之前写vector的区间构造也提到过,我们可以以下列方式来初始化vector的一堆值:
vector<int>v = { 1,2,3 };
这种形式和上述的隐式类型转换相似又不一致,确实是调用了vector的构造函数,具体是:

没错,这里其实是传入了迭代器来进行初始化。
C++11中将{}封装成了一个类:
auto x = { 1,2,3 };
cout << typeid(x).name() << endl;
结果:
class std::initializer_list<int>
也就是说所有花括号都是initializer_list这个类,上面vector的初始化,实际上就是传入initializer_list的迭代器来实现的。
具体应用
有了这些语法,我们能实现部分容器的高效初始化:
map<string, int>m = {{"apple",19},{"banana",10},{"Love",0}
};
可以看到,这里其实是首先从{“apple”,19}隐式类型转换为pair<string,int>,然后再根据initializer_list初始化到m上。
声明
C++11增加了许多简化声明的方式。
auto
auto关键字前面已经讲过了,可以自动推导类型。像:
int add(int x, int y)
{return x + y;
}
int main()
{string str{ "love" };auto ss = str;auto func = add;cout << typeid(ss).name() << endl;cout << typeid(func).name() << endl;return 0;
}
结果:
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
int (__cdecl*)(int,int)
除此之外,auto还可以当作返回值类型:
auto minus(int x, int y)
{return x - y;
}
int main()
{cout << ::minus(10, 6) << endl;return 0;
}
结果:
4
当然了,auto不能作参数列表。
int times(int x,auto y)
{return x * y;
}
error C3533: 参数不能为包含“auto”的类型
事实上我们有模板功能来实现接入参数的类型随机性,也不需要auto来实现。
decltype
decltype顾名思义就是declaim type,获取类型。不同于typied.name()得到的仅是字符串,decltype得到的类型名是可以直接当作类型使用的:
template<class T1, class T2>
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret;cout << typeid(ret).name() << endl;
}
int main()
{const int x = 1;double y = 2.2;decltype(x * y) ret; // ret的类型是doubledecltype(&x) p;// p的类型是int*cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;F(1, 'a');
}
运行结果:
double
int const * __ptr64
int
nullptr
在C语言中,NULL表示空指针,但是NULL同时又表示整数0.
考虑到程序的安全性,C++11以nullptr形式定义空指针,实际上就是(void*)0;
范围for
范围for也是C++11新增的语法,前面实现各种容器的时候已经讲过。只要容器实现了迭代器都能使用范围for。以下是一个vector的例子:
vector<int> v = { 1,2,3,4,5 };
for (auto& e : v)
{cout << e << ' ';
}
cout << endl;
相当于:
vector<int> v = { 1,2,3,4,5 };
auto it = v.begin();
while (it != v.end())
{cout << *it << ' ' << endl;it++;
}
cout << endl;
结果都是:
1
2
3
4
5
智能指针
这部分篇幅较长,在后续文章再进行讲解。
STL新增容器
C++11在STL中增加了新的容器:array、forward_list、unordered_set、unordered_map
具体使用就不多介绍了。
右值引用
右值引用则是C++11中新增的相当重要的语法。
左值和右值
首先需要明确何为左值何为右值?
简单来说,左值就是可以取地址的数值表达式,右值就是不能取地址的数值表达式
如:
int main()
{int* p = new int(0);int b = 1;const int c = 2;cout << &p << endl << &b << endl << &c << endl;return 0;
}
结果:
000000D91AEFF5C8
000000D91AEFF5E4
000000D91AEFF604
可以看到p、b、c这些都是左值。
常见的右值有:字面常量、表达式返回值、函数返回值
int add(int x, int y)
{return x + y;
}int main()
{cout << &10 << endl;cout << &(2 - 1) << endl;cout << &add(2, 10) << endl;
}
结果:
error C2101: 常量上的“&”error C2101: 常量上的“&”error C2102: “&”要求左值
其中 error C2102明确表明了,只有左值才能取地址。
不过这里要郑重申明,右值只是不能取地址,不代表没有地址
左值引用和右值引用
左值引用自然就是对左值的引用,右值引用就是对右值的引用:
左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
右值引用
int&& rr1 = 10;
int&& rr2 = 2 - 1;
int&& rr3 = add(2, 10);
可以看到,右值引用是类型名+“&&”
接下来我们需要考虑两件事情,那就是左值引用能否引用右值,右值引用能否引用左值?
int& rr1 = 10+2;
int x = 20;
int&& y = x;
error C2440: “初始化”: 无法从“int”转换为“int &”
error C2440: “初始化”: 无法从“int”转换为“int &&”
可以看到左值引用直接引用右值和右值引用直接引用左值都会报错,那是否没有办法了,自然不是。
const int& rr1 = 10 + 2;
int x = 20;
int&& y = move(x);
像这种形式就可以通过编译了。也就是说const 左值引用可以引用左值也可以引用右值,右值引用只能应用右值或者move以后的左值。
这里的move函数实际上就是将左值转换成右值。
- 还有一点比较重要的。我们都知道左值引用本身也是左值,那右值引用是左值还是右值呢?
int main()
{int&& x = 10;cout << &x << endl;return 0;
}
000000CE6D8FF614
可以看到,右值引用本身也是左值。同时也证明了右值确实也有地址。
总结:
- 左值引用只能引用左值,不能引用右值。
- 但是const左值引用既可引用左值,也可引用右值。
- 右值引用只能右值,不能引用左值。
- 但是右值引用可以move以后的左值。
移动构造和移动赋值
右值其实又细分为纯右值和将亡值。纯右值是指字面常量那些,将亡值则是指临时变量或传值返回。
基于将亡值即将销毁的特性,我们可以利用其资源来完成构造和拷贝:
void swap(string& s)
{::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);
}string(string&& s):_str(nullptr),_size(0),_capacity(0)
{cout << "移动构造" << endl;swap(s);
}string& operator=(string&& s)
{cout << "string& operator=(string&& s)" << endl;swap(s);return *this;
}
是的我们直接将右值的资源换给了左值,右值可以安心去世了。
左右值引用使用场景
首先明确下,引用的意义是减少拷贝,提高效率。
但是对于一些函数返回值是常量拷贝时,左值引用就派不上用场了。
基于前文实现的string的实现,我们来观测一下下面情况:
mystring::string ret1;
ret1 = mystring::string::to_string(1234);
string(char* str) -- 构造
string(char* str) -- 构造
string& operator=(string s) -- 深拷贝
可以看到,先是对ret1进行了一次构造,然后在to_string函数里面又进行了一次构造。实际上最后应该进行两次拷贝构造,分别是to_string函数里string返回时,拷贝给一个临时变量,临时变量再拷贝给ret1.
当然两次拷贝构造被编译器优化成了一次,如果是下面这样写的话,优化更甚:
mystring::string ret2 = mystring::string::to_string(1234);
cout << ret2.c_str() << endl;
string(char* str) -- 构造
1234
可以看到,他竟然优化成了一次构造,不得不说现在的编译器是真的厉害啊。
回到原文,考虑一开始的情况。可以发现to_string返回的是值,对他进行深拷贝然后再深拷贝给ret1代价无疑非常大。如果我们实现了移动构造,那么代价就可以缩小:
mystring::string ret1;
ret1 = mystring::string::to_string(1234);
string(char* str) -- 构造
string(char* str) -- 构造
string(string&& s) -- 移动赋值
可以看到拷贝构造变成了赋值构造,代价大大降低了。
当然了,我们可不要做一个喜新厌旧的人。移动拷贝并非所有情况都是最优的。我们之所以用移动构造代替拷贝构造能提升效率,本质上拷贝构造调的是深拷贝,如果是浅拷贝的话拷贝构造效率自然会比移动构造高。
完美转发
前面提到过,右值引用本身会退化成左值,这就为某些事请埋下了伏笔。
且看下面函数:
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
首先说明下:
template
void PerfectForward(T&& t)
这里T&&可不是指T类型的右值引用,这里是万能引用模板的格式。当传入左值引用就会识别成左值引用,传入右值引用就会识别成右值引用。
那么来看看代码的运行结果吧:
左值引用
左值引用
左值引用
const 左值引用
const 左值引用
可以看到由于右值引用退化成了左值,调用Fun函数变成了全部都是左值函数了。
当然了我们可以强制类型转换一下:
template<typename T>
void PerfectForward(T&& t)
{Fun((T&&)t);
}
右值引用
左值引用
右值引用
const 左值引用
const 右值引用
结果自然正确了,但这不是C++11提供的最佳方案。C++11提供了完美转发函数forward能完美传递对应的左右值类型,使用方法如下:
template<typename T>
void PerfectForward(T&& t)
{Fun(forward<T>(t));
}
右值引用
左值引用
右值引用
const 左值引用
const 右值引用
可以看到forward其实是个仿函数,最终也实现了类型的完美对应。
lambda表达式
lambda表达式同样是C++11中新增的及其重要的语法。
使用格式
首先声明,lambda表达式本质就是一个匿名函数。它可以代替仿函数作为传入参数,节省了写仿函数的功夫。
先看具体格式:
[capture-list] (parameters) mutable -> return-type { statement }
int main()
{auto func1 = [](int x, int y)->int {return x + y; };auto func2 = [](int x, int y){return x + y; };auto func3 = [] {cout << "Love" << endl; };cout << func1(10, 2) << endl << func2(20, 10) << endl;func3();return 0;
}
12
30
Love
可以看到,返回值类型可以自动推导因此可以省略。如果没有参数,甚至可以省略参数列表。
应用
struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价// ...Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};void show(vector<Goods>& v)
{for (auto& e : v){cout << e._name << ' ';}cout << endl;
}int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };auto priceLess = [](const Goods& g1, const Goods& g2){return g1._price < g2._price;};sort(v.begin(), v.end(), priceLess);show(v);sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price;});show(v);sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._evaluate < g2._evaluate;});show(v);sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._evaluate > g2._evaluate;});show(v);return 0;
}
菠萝 苹果 橙子 香蕉
香蕉 橙子 苹果 菠萝
橙子 香蕉 菠萝 苹果
苹果 香蕉 菠萝 橙子
可以看到,不需要写仿函数就能快速调用类似sort的函数,还是非常方便的。
捕捉列表
捕捉列表,可以捕捉当前作用域的参数
int main()
{int a = 10;auto func = [a] {cout << a << endl; };func();return 0;
}
10
不过这里默认是不可修改捕捉值的:
auto func = [a] {a = 19; };
“a”: 无法在非可变 lambda 中修改通过复制捕获
需要加上mutable关键字
int main()
{int a = 10;auto func = [a]()mutable {a = 19; cout << a << endl; };func();cout << a << endl;return 0;
}
19
10
可以看到这并没有多大的意义,毕竟是传值捕捉。如果想要修改对应作用域的值,需要引用捕捉。
int main()
{int a = 10;auto func = [&a](){a = 19; cout << a << endl; };func();cout << a << endl;return 0;
}
19
19
此外[=]表示传值捕捉当前作用域所有变量,[&]表示引用捕捉当前作用域所有变量,当然还可以混合捕捉,具体看下面代码:
int main()
{int a = 1, b = 2, c = 3, d = 4, e = 5;// 传值捕捉所有对象auto func1 = [=](){return a + b + c * d;};cout << func1() << endl;// 传引用捕捉所有对象auto func2 = [&](){a++;b++;c++;d++;e++;};func2();cout << a << b << c << d << e << endl;// 混合捕捉,传引用捕捉所有对象,但是d和e传值捕捉auto func3 = [&, d, e](){a++;b++;c++;//d++;//e++;};func3();cout << a << b << c << d << e << endl;// a b传引用捕捉,d和e传值捕捉auto func4 = [&a, &b, d, e]() mutable{a++;b++;d++;e++;};func4();cout << a << b << c << d << e << endl;return 0;
}
15
23456
34556
45556
类的新功能
类和对象中提到,类有六个默认成员函数
- 构造函数
- 拷贝构造
- 赋值重载
- 析构函数
- &重载
- cosnt &重载
现在C++11新增了两个默认成员函数,分别是移动构造和移动赋值。
现在这两个默认函数有以下特点:
- 当你没有实现析构函数、拷贝函数、拷贝赋值重载中的任意一个函数,编译器会生成默认的移动构造和移动赋值。
- 如果你提供了移动构造和移动赋值,那么编译器不会提供拷贝和拷贝赋值。
可变参数模板
C++11允许你创建可以接受可变参数的函数模板和类模板,具体格式如下:
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
可以看到可变参数模板其实有一个相当晦涩的语法表达式。
那么接下来有个严肃的问题,既然有了参数包,要怎么获取其中的参数呢?相信很多人第一时间想到了下列方案:
template <class ...Args>
void Cpp_Printf(Args... args)
{// 计算参数包的数据个数cout << sizeof...(args) << endl;for (size_t i = 0; i < sizeof...(args); i++){cout << args[i] << endl;}cout << endl;
}
int main()
{Cpp_Printf(1, 2);return 0;
}
事实上单单计算参数包的数据个数还是能跑通的,但如果加上下面那个循环体,结果就是
error C3520: “args”: 必须在此上下文中扩展参数包
事实上我也不是很理解为什么这样是不行的,但总之就是不行
正确的方法有两种
- 以递归形式拆解拓展包
template <class T>
void Cpp_Printf(T x)
{cout << x << endl;
}template <class T, class ...Args>
void Cpp_Printf(T x,Args... args)
{cout << x << ' ';Cpp_Printf(args...);
}int main()
{Cpp_Printf(1, 2, 3, 4, 5, 6);Cpp_Printf(520, "Love", 3.14159, 'M');return 0;
}
1 2 3 4 5 6
520 Love 3.14159 M
当然了,如果你觉得这种展开方式过于晦涩,我们还有更晦涩的另一种方式:
- 逗号表达式展开
template <class T>
void Cpp_Printf(T x)
{cout << x << ' ';
}template < class ...Args>
void Cpp_Printf( Args... args)
{int arr[] = { (Cpp_Printf(args),0)... };cout << endl;
}int main()
{Cpp_Printf(1, 2, 3, 4, 5, 6);Cpp_Printf(520, "Love", 3.14159, 'M');return 0;
}
1 2 3 4 5 6
520 Love 3.14159 M
我们来研究一下这个是怎么做到的。首先他用到了c++11中以列表形式初始化int数组arr。
然后编译器在编译过程中就展开了参数包(Cpp_Printf(args),0)…,感觉上是以旧的参数包构建了新的参数包,然后在数组的初始化列表中展开。
随后由于展开的是逗号表达式,因此会先执行逗号前面的项,最后得到0.
因此在展开过程中实际上已经调用了函数Cpp_Printf。
此外逗号后面写其他整数也是可以的。
容器的emplace接口
C++11中的容器都新增了emplace接口,例如vector:


可以看到emplace_back的参数列表是可变参数,实际上emplace_back如果传入的是vector<T>的T类型,那本质上和push_back没有区别。
例如:
int main()
{pair<string, int> p{ "Love",10 };vector<pair<string, int>> v;v.push_back(p);v.emplace_back(p);
}
像这样,就是先调用p的构造,然后pushback和emplaceback时调用拷贝构造。
但是emplace_back还可以这样调用:
v.emplace_back("love u",10000);
这个参数包会层层下传,最后只调用一次构造。
因此总的来说emplace系列比push和insert系列效率是要高的。
包装器
C++11中可以起到调用函数功能的有:函数指针、仿函数、lambda表达式。类型混乱无章,无法统一调配。因此需要对他们包装一下,至少使得他们的外表是同一个类,也就是所谓的包装器。
function包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
先看使用格式:
std::function在头文件<functional>// 类模板原型如下
template <class T> function;
// undefinedtemplate <class Ret, class... Args>class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
可以看到,这里的模板显示实例化和一般格式不相同,但其实更方便理解,在()前的是函数返回值类型,括号中间则是返回值列表。
那么先来简单使用一下吧:
int f(int a, int b)
{return a + b;
}struct Functor
{
public:int operator() (int a, int b){return a + b;}
};int main()
{function<int(int, int)> fc2 = f;function<int(int, int)> fc3 = Functor();function<int(int, int)> fc4 = [](int x, int y) {return x + y;};cout << fc2(1, 2) << endl;cout << fc3(1, 2) << endl;cout << fc4(1, 2) << endl;return 0;
}
3
3
3
funtionc还有一些有实际意义的使用场景:
template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}
像这种,虽然说传入函数指针、仿函数、lambda表达式都是对的,但编译器也因此实现了三份不同的代码,自然是浪费空间的。如果统一成funtion<double(double)>格式自然能减少麻烦的。
bind包装器
除了function包装器,还有bind包装器。能够调整参数顺序和个数,具体使用格式:
// 原型如下:
template <class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);// with return type (2)
template <class Ret, class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);
具体使用:
class Sub
{
public:Sub(int x):_x(x){}int sub(int a, int b){return (a - b) * _x;}private:int _x;
};auto f3 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3);cout << f3(Sub(1), 10, 5) << endl;Sub sub(1);cout << f3(&sub, 10, 5) << endl;// 绑定,调整参数个数auto f4 = bind(&Sub::sub, Sub(1), placeholders::_1, placeholders::_2);cout << f4(10, 5) << endl;auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2);cout << f5(10, 5) << endl;
我们通过placeholders命名空间下的_1、_2、…、_n、…来控制参数顺序,以及想绑定的参数。
从上面的例子可以看到,正常类成员函数是要传入隐藏的this指针,因此我们可以在第一个参数的位置上绑定一个类指针或者类实例。
如果说上面的例子还不够直观,那么我们来看对于sub函数的各种绑定情况:
int main()
{//无任何改变auto f1 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3);cout << f1(Sub(1), 10, 5) << endl;//绑定类指针auto f2 = bind(&Sub::sub,&Sub(1),placeholders::_1, placeholders::_2);cout << f2(10, 5) << endl;//绑定类实例对象auto f3 = bind(&Sub::sub, Sub(1), placeholders::_1, placeholders::_2);cout << f3(10, 5) << endl;//绑定被减数auto f4 = bind(&Sub::sub, placeholders::_1, 100, placeholders::_2);cout << f4(Sub(1), 5) << endl;//调整被减数、减数顺序auto f5 = bind(&Sub::sub, placeholders::_1, placeholders::_3, placeholders::_2);cout << f5(Sub(1), 100, 5) << endl;return 0;
}
5
5
5
95
-95
这下够直观显然了吧。
线程库
在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。
使用线程库前先包含<thread>头文件
| 函数名 | 功能 |
|---|---|
| thread() | 构造一个线程对象,没有关联任何线程函数,即没有启动任何线程 |
| thread(fn,args1, args2,…) | 构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数的参数 |
| get_id() | 获取线程id |
| jionable() | 线程是否还在执行,joinable代表的是一个正在执行中的线程。 |
| jion() | 该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行 |
| detach() | 在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关 |
先来看看简单的使用:
void Print(int n, int i)
{for (; i < n; i++){cout << i << endl;}cout << endl;
}int main()
{thread t1(Print, 100, 0);thread t2(Print, 200, 100);cout << t1.get_id() << endl;cout << t2.get_id() << endl;t1.join();t2.join();cout << this_thread::get_id() << endl;return 0;
}
结果自然是很混乱,因为我们没有给x上锁,现在上锁改进下.
void Print(int n, int& rx, mutex& rmtx)
{ rmtx.lock();for (int i = 0; i < n; i++){// t1 t2++rx;}rmtx.unlock();
}int main()
{int x = 0;mutex mtx;thread t1(Print, 1000000, ref(x), ref(mtx));thread t2(Print, 2000000, ref(x), ref(mtx));t1.join();t2.join();cout << x << endl;return 0;
}
3000000
结果很棒,但考虑到有可能跑线程时挂掉了,rmtx就一直锁着,就会导致程序死锁。
因此前辈考虑到用类的构造和析构函数形式来上锁和解锁:
class LockGuard
{
public:LockGuard(mutex& mtx):_mtx(mtx){_mtx.lock();}~LockGuard(){_mtx.unlock();}
private:mutex& _mtx;
};
类似这般使用:
//局部域
{LockGuard lock(mtx);lock_guard<mutex> lock(mtx);for (size_t i = 0; i < n; i++){++x;}
}
那么有了线程库自然能实现常见的代码:两个线程交替打印奇偶数,
void two_thread_print()
{std::mutex mtx;condition_variable c;int n = 100;bool flag = true;thread t1([&]() {int i = 0;while (i < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return flag; });cout << i << endl;flag = false;i += 2; // 偶数c.notify_one();}});thread t2([&]() {int j = 1;while (j < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return !flag; });cout << j << endl;j += 2; // 奇数flag = true;c.notify_one();}});t1.join();t2.join();
}
int main()
{two_thread_print();return 0;
}
各种奥秘,请读者自行参悟.
