C++11新特性介绍
文章目录
- 1. C++11 简介
- 1.1 C++语言的发展历程
- 1.2 C++11主要改进
- 2. 统一的列表初始化
- 2.1 列表初始化
- 2.2 std::initializer_list
- 3. 声明
- 3.1 auto 类型推导
- 3.2 decltype 类型声明
- 3.3 nullptr
- 4. 范围for循环
- 5. 智能指针
- 6. STL中的变化
- 6.1 新容器
- 6.2 新方法
- 7. 右值引用和移动语义
- 7.1 基本概念
- 7.2 移动构造和移动赋值
- 7.3 std::move
- 7.4 完美转发
- 8. 新的类功能
- 8.1 默认成员函数
- 8.2 类成员变量初始化
- 8.3 default 和 delete
- 9. 可变参数模板
- 9.1 基本语法
- 9.2 参数包展开方式
- 9.3 emplace系列函数
- 10. lambda表达式
- 10.1 语法格式
- 10.2 捕获列表
- 10.3 使用示例
- 10.4 lambda与函数对象
- 11. 包装器
- 11.1 function包装器
- 11.2 bind绑定器
- 12. 线程库
- 12.1 thread类基本使用
- 12.2 线程参数传递
- 12.3 原子操作
- 12.4 锁管理
- 12.5 条件变量
1. C++11 简介
1.1 C++语言的发展历程
- C++98/03:C++的第一个国际标准,C++03主要是对C++98的漏洞修复
- C++11:第二个真正意义上的标准,带来了约140个新特性,修正了约600个缺陷
- 命名由来:原计划2007年发布叫C++07,后改为C++0x,最终2011年发布定名C++11
1.2 C++11主要改进
- 更好地用于系统开发和库开发
- 语法更加泛化和简单化
- 更加稳定和安全
- 提升程序员开发效率
2. 统一的列表初始化
2.1 列表初始化
C++11扩大了花括号初始化列表的使用范围,达到了“万物皆可列表初始化”:
// C++98 中的列表初始化
int array1[] = {1, 2, 3, 4, 5};
Point p = {1, 2};// C++11 扩展
int x1 = 1;
int x2{2}; // 内置类型
int array1[]{1, 2, 3, 4, 5}; // 数组
Point p{1, 2}; // 结构体
int* pa = new int[4]{0}; // new表达式// 类对象初始化
Date d1(2022, 1, 1); // 传统方式
Date d2{2022, 1, 2}; // 列表初始化
Date d3 = {2022, 1, 3}; // 带等号的列表初始化
这种机制的实现依赖这底层的initializer_list,将我们写的列表转为initializer_list对象,自定义类型底层重载了initializer_list的拷贝构造,内部去遍历该initializer_list对象来初始化我们的数据。
2.2 std::initializer_list
initializer_list的定义:一种标准库类型,用于表示花括号包围的值列表,并且支持迭代器,让我们得以遍历
auto i1 = {10, 20, 30}; // i1的类型是std::initializer_list<int>
使用场景:
- 作为构造函数的参数
- 作为operator=的参数
// STL容器使用initializer_list初始化
vector<int> v = {1, 2, 3, 4};
list<int> lt = {1, 2};
map<string, string> dict = {{"sort", "排序"}, {"insert", "插入"}};// 自定义vector实现
template<class T>
class vector {
public:vector(initializer_list<T> l) {_start = new T[l.size()];_finish = _start + l.size();_endofstorage = _start + l.size();iterator vit = _start;typename initializer_list<T>::iterator lit = l.begin();while (lit != l.end()) {*vit++ = *lit++;}}vector<T>& operator=(initializer_list<T> l) {vector<T> tmp(l);std::swap(_start, tmp._start);// ... 其他交换操作return *this;}
};
3. 声明
3.1 auto 类型推导
作用:让编译器自动推导变量类型
int i = 10;
auto p = &i; // p的类型是int*
auto pf = strcpy; // pf的类型是函数指针
map<string, string> dict = {{"sort", "排序"}};
auto it = dict.begin(); // 自动推导迭代器类型
要求:必须进行显式初始化
3.2 decltype 类型声明
作用:声明变量的类型为指定表达式的类型
const int x = 1;
double y = 2.2;decltype(x * y) ret; // ret的类型是double
decltype(&x) p; // p的类型是int*// 模板中使用
template<class T1, class T2>
void F(T1 t1, T2 t2) {decltype(t1 * t2) ret; // 根据表达式结果确定类型cout << typeid(ret).name() << endl;
}
3.3 nullptr
解决的问题:C++中NULL被定义为字面量0,可能带来歧义,因为无法区分类型是指针还是整形,在一些泛型编程中,可能会被识别为int类型,而我们可能希望其被识别为指针类型,因此引入nullptr明确表示空指针
// 传统NULL的问题
#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif// C++11解决方案
int* ptr = nullptr; // 明确的空指针
4. 范围for循环
依赖于迭代器而实现的自动遍历
vector<int> ve = {1,2,3,4,5};//第一种遍历方式,手动遍历for(int i = 0; i < ve.size(); i++){cout << ve[i] << endl;ve[i] *= 2;}//第二种遍历方式,迭代器vector<int>::iterator it = ve.begin();while(it != ve.end()){cout << *it << endl;*it *= 2;}//第三种,范围forfor(int e : ve) //第1种,赋值属性,e为ve底层元素的拷贝,其修改与底层元素无关{cout << e << endl;e *= 2;}for(int& e : ve) //第2种,引用属性,可读可写,e就是ve底层的元素别名{cout << e << endl;e *= 2;}
可以看到,范围for可以更加简洁的遍历容器,他的底层实现依赖迭代器,可以认为,我们在语法层面上写出范围for,编译器会将其转为第二种遍历方式迭代器的遍历。范围for的第2中引用类型的遍历,直接对标第二种迭代器的方式,而第1中则是多了一层赋值,将其赋值给变量e在给我们返回。
在实际中我们可以搭配auto使用
for(auto e : ve)
{
cout << e << endl;
e *= 2;
}for(auto& e : ve)
{
cout << e << endl;
e *= 2;
}
5. 智能指针
智能指针部分讲解复杂,本文暂不做介绍,后续会更新专门讲解的文章,敬请关注
6. STL中的变化
6.1 新容器
- < array >:静态数组
- <forward_list>:单向链表
- <unordered_map>:无序映射
- <unordered_set>:无序集合
unordered_map和unordered_set在实际开发中具有很大的价值。
6.2 新方法
- cbegin() / cend():返回const迭代器
- 右值引用版本的插入接口:
- emplace_back()
- emplace()
- push_back()的右值引用版本
7. 右值引用和移动语义
7.1 基本概念
左值右值,这里的值不仅是字面常量,算数结果等,只要是表达式,可以表达值都可以算是值,比如运算表达式,函数调用的返回值等
左值:可以取地址、有名字的表达式
int a = 10; // a是左值
int* p = &a; // p是左值
*p = 20; // *p是左值
右值:不能取地址、临时性的表达式
右值还分为纯右值(内置类型)和将亡值(自定义类型,比如匿名对象,传值返回生成的临时对象等即将“死亡”的对象)
右值还有着不能被修改的属性(可以理解为临时变量具有常性)。
10; // 字面常量
x + y; // 表达式返回值
fmin(x, y); // 函数返回值(非引用返回)
左值引用 vs 右值引用:
// 左值引用
int a = 10;
int& ra = a; // 正确
// int& rb = 10; // 错误,不能引用右值
const int& rc = 10; // 正确,const左值引用可以绑定右值// 右值引用
int&& rr1 = 10; // 正确
double&& rr2 = x + y; // 正确
// int&& rr3 = a; // 错误,不能引用左值
int&& rr4 = std::move(a); // 正确,move将左值转为右值
**右值引用本身是左值属性!**可以进行取地址,可以进行资源转移,资源转移本身也是一种数据的修改,所以说不能是右值属性,因为右值属性无法修改。右值引用后进行移动构造或者移动赋值进行资源转移,就注定了右值引用必须是左值属性。
可以认为,右值引用是高级版的const引用,因为右值引用是可以修改的左值,const引用是不可以修改的左值。
const string& s1 = string("1111");
s1[0] = '2'; //错误,const引用不可以修改string&& s2 = string("2222");
s2[0] = '1'; //可以,该引用具有左值属性,可以修改
可以看到,匿名对象被引用后依然存活,也就是说,引用也可以延长匿名对象(临时变量)的生命周期,直到引用的变量结束为止。
7.2 移动构造和移动赋值
**移动构造:将右值(将亡值)的资源移动到左值对象上。**可以消除函数返回自定义类型对象时产生的非必要拷贝开销
在未引入移动语义前,函数通过传值方式返回对象时,编译器需要将函数内部的局部对象(左值)复制到外部接收对象中。由于函数栈帧销毁时局部对象随之析构,此过程无法避免地触发拷贝构造函数,导致深层复制,效率低下。虽然传引用返回可规避拷贝,但会引发对已销毁对象的访问,产生类似野指针的问题。
移动构造函数通过接收右值引用参数,直接接管源对象所管理的资源(内部的指针等)。该过程仅涉及内部指针的交换与状态重置,将原需深层复制的操作转换为常数时间复杂度的资源转移。被移动后的源对象(将亡值)处于有效但资源为空的状态,其析构过程不会影响新对象。
现代编译器会执行返回值优化,即使在语法上返回的是左值,也可能直接调用移动构造。我们自己也可使用 std::move 显式指示编译器将左值作为右值处理,强制启用移动语义。需要注意的是,被移动后的对象不应再被访问,因其资源已被转移。
通过所有权转移而非内容复制的机制,移动构造显著提升了大型对象传递的效率。
对于提升效率,针对的是具有深拷贝的自定义类型,因为不存在深拷贝的类型没有资源的说法,任何拷贝都需要浅拷贝值。因此深拷贝的类才有转移资源的移动系列函数,对于内置类型和浅拷贝的类并没有移动系类的函数,自己也可以写,但没有意义。
移动构造函数:
class string {
public:// 移动构造string(string&& s): _str(nullptr), _size(0), _capacity(0) {cout << "string(string&& s) -- 移动语义" << endl;swap(s); // 窃取资源}// 移动赋值string& operator=(string&& s) {cout << "string& operator=(string&& s) -- 移动语义" << endl;swap(s);return *this;}
};
应用场景:
bit::string to_string(int value) {bit::string str;// ... 处理逻辑return str; // 这里会调用移动构造而不是拷贝构造
}int main() {My'string::string ret = bit::to_string(1234); // 移动构造ret = bit::to_string(5678); // 移动赋值
}
7.3 std::move
作用:将左值强制转换为右值引用
bit::string s1("hello");
bit::string s2(s1); // 拷贝构造
bit::string s3(std::move(s1)); // 移动构造,s1资源被转移
7.4 完美转发
对于引用类型,还可以进行细分,分为左值引用(int&),const左值引用(const int&),右值引用(int&&),const右值引用(const int&&)。那么在参数传递的时候就需要写出对应的版本,4个版本写起来有点冗余,因此可以用万能引用,只针对模板函数,可以帮我们实例化出不同引用类型的版本,传递什么实例化什么。
万能引用:在函数模板中使用T&&可以接受任意引用类型,会自动进行推导。
template
void PerfectForward(T&& t);
但是这样也会遇到问题,在函数内部,t会变成左值,因为右值引用本身也是左值属性,解决方法如下:
现象展示:函数模板中的万能引用会丢失参数的左右值属性
template<typename T>
void PerfectForward(T&& t) {Fun(t); // 无论t是左值还是右值,这里都会当作左值处理
}
解决方案:std::forward保持参数的原始属性
template<typename T>
void PerfectForward(T&& t) {Fun(std::forward<T>(t)); // 保持参数的左右值属性
}
实际应用:
template<class T>
class List {
public:void PushBack(T&& x) {Insert(_head, std::forward<T>(x)); // 完美转发}void Insert(Node* pos, T&& x) {Node* newnode = new Node;newnode->_data = std::forward<T>(x); // 关键位置// ... 链接操作}
};
8. 新的类功能
8.1 默认成员函数
C++11新增两个默认成员函数:
- 移动构造函数
- 移动赋值运算符重载
生成规则:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个(三个都没实现)。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内 置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋 值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造 完全类似)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
通常来说,一个类如果需要手动写析构函数,那他就是深拷贝类型的类,那么通常来说也需要写拷贝构造,拷贝赋值重载,同时也要手动写移动构造。
8.2 类成员变量初始化
class Person {
private:bit::string _name = "unknown"; // C++11允许在类定义时给成员初始缺省值int _age = 0;
};
8.3 default 和 delete
default:强制生成默认函数(8个默认成员函数)
但是强制默认生成了移动构造,那么默认的拷贝构造将不会生成,移动赋值也是相同,因此在强制生成移动构造和移动赋值的时候也要强制生成拷贝构造和赋值重载。
class Person {
public:Person(const Person& p) = default; // 强制生成拷贝构造Person(Person&& p) = default; // 强制生成移动构造
};
delete:禁止生成默认函数
class Person {
public:Person(const Person& p) = delete; // 禁止生成默认拷贝构造
};
9. 可变参数模板
9.1 基本语法
template <class ...Args>
void ShowList(Args... args) {// 参数包中可以包含0到任意个模板参数
}
使用sizeof可以计算参数个数
sizeof…(args);
参数包在编译时进行解析
9.2 参数包展开方式
递归展开:
可以在设计一个模板函数,模板参数第一个为一个T,第二个为参数包Args,在函数体内,第一个T就是上层传递参数包的第一个参数,Args就是从第二个参数开始的上层参数包,再去调用该函数,就可以逐步展开。
并且得加上递归终止函数,参数包在传递到最后只剩一个参数,因此需要在重载一个单参数的递归函数来充当终止函数。也可以使用无参的函数,因为参数包最后一个传递给T,剩余参数为空也可以传递。
代码如下
// 递归终止函数
template <class T>
void ShowList(const T& t) {cout << t << endl;
}//终止函数也可以这样写
void ShowList() {
}// 展开函数
template <class T, class ...Args> //每次都会将上层的参数包的第一个参数传给T
void ShowList(T value, Args... args) {cout << value << " ";ShowList(args...); // 递归展开
}template <class ...Args>
void Cpp_showArgs( Args... args) {ShowList(args...); //调用递归函数
}
以上递归是在编译时确定推导出的,并且运行时的结果
利用数组展开:
template <class T>
int PrintArg(T t) {cout << t << " ";return 0;
}template <class ...Args>
void ShowList(Args... args) {int arr[] = { PrintArg(args)... }; // 利用数组初始化展开参数包cout << endl;
}int main()
{ShowList(1,'b',string("xxx"));return 0;
}//编译器在编译的时候会转为下列函数:
void ShowList(int x, char y, string z) //编译的时候参数包会展开
{ //int arr[] = { PrintArg(args)... }; // 数组定义需要知道大小,就回看列表里有多少数据,就回展开参数包int arr[] = {PrintArg(x), PrintArg(y),PrintArg(z)};//函数执行完之后,在函数体内完成打印,数组里面存放的都是0cout << endl;
}
在实际中可能不去一个一个展开参数宝,而是层层转发,到了最底层直接使用,就像emplace系列函数,传到最底层直接进行构造
9.3 emplace系列函数
emplace系列函数相比push系列函数的核心优势在于其构造对象的方式更加高效直接。
当我们使用push函数时,push的参数已经固定了,就是其所存储的元素类型,因此通常需要先在外部构造一个完整的对象实例(可以是有名的,也可以是匿名的临时变量),然后再将这个对象传递到容器内部,用这个对象再去构造出容器内真正存储的对象。这个过程不可避免地会产生一次拷贝构造或移动构造的开销。
而emplace函数则采用了更为巧妙的实现方式,它通过完美转发技术,直接将构造对象所需的参数传递给容器,让对象在容器的内存空间中原地构造。这种一步到位的构造方式完全跳过了创建临时对象的中间环节,既避免了不必要的拷贝操作,也省去了移动构造的开销,特别对于构造成本较高的对象来说,这种效率提升尤为明显。emplace函数实现了从参数到最终对象的直接转换,为容器插入操作提供了最优的性能路径。
构造成本高的对象通常来说是左值或者没有移动构造的右值。
对于有移动构造的右值的插入,其实没有很大的效率差距,因为右值会调用移动构造,直接转移资源,相比直接插入只多了一个交换资源。
但是对于左值或者没有移动构造的右值,直接省去拷贝构造,会有明显的效率提升。
std::list<std::pair<int, char>> mylist;
mylist.emplace_back(10, 'a'); // 直接构造,避免拷贝
mylist.push_back({20, 'b'}); // 先构造pair,再移动
10. lambda表达式
C++中的Lambda表达式是一种内联定义的匿名函数对象,它通过 [ 捕获列表 ] ( 参数 ) { 函数体 }的简洁语法允许开发者在需要函数的地方直接定义逻辑,特别适用于STL算法、回调函数和异步编程等场景。Lambda可以捕获外部变量形成闭包,支持值捕获、引用捕获和混合捕获方式,在底层被编译器转换为匿名的函数对象类并重载operator()运算符,既保持了代码的简洁性和可读性,又能享受编译器的内联优化,是现代C++函数式编程和并发编程的重要工具。
10.1 语法格式
[capture-list](parameters) mutable -> return-type { statement }
各部分说明:
[capture-list]:捕捉列表
(parameters):参数列表
mutable:取消常量性
-> return-type:返回值类型
{ statement }:函数体
10.2 捕获列表
Lambda表达式的捕获列表就像是给这个匿名函数函数体内使用外部作用于变量的权力,即不通过传参,访问函数外(定义该lambda的作用域)的变量。告诉它可以使用外部的哪些变量以及如何使用。这个列表放在Lambda开头的那对中括号里,有几种不同的设置方式:
你可以明确指定要使用的变量,比如[x]表示把变量x的值复制一份给Lambda使用,Lambda内部怎么修改都不会影响外面的x;而[&y]表示Lambda可以直接使用外面的变量y,如果在Lambda里修改了y,外面的y也会跟着改变。
如果你需要使用的外部变量太多,也可以不一一列出来,可以用简写方式:[=]表示把当前作用域里所有能用的变量都复制一份给Lambda;[&]表示Lambda可以直接使用和修改所有外部变量。
还可以混合使用,比如[=, &z]表示除了z是按引用方式(可以修改)外,其他变量都是按值复制。需要注意的是,按值复制的变量在Lambda内部默认是不能修改的(相当于传参 在类型前加const ),如果你确实需要修改它们,就要在参数列表后面加上mutable关键字。
[var]:值传递捕捉变量var
[=]:值传递捕捉所有父作用域变量
[&var]:引用传递捕捉变量var
[&]:引用传递捕捉所有父作用域变量
[this]:值传递捕捉当前this指针
10.3 使用示例
int main() {int a = 3, b = 4;// 最简单的lambda[]{};// 自动推导返回值[=]{ return a + 3; };// 引用捕获auto fun1 = [&](int c){ b = a + c; };fun1(10);// 完整形式auto fun2 = [=, &b](int c)->int{ return b += a + c; };// mutable修改值捕获的变量int x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };
}
10.4 lambda与函数对象
底层实现:在编译时编译器将lambda表达式转换为对应仿函数
// lambda表达式
auto r2 = [=](double money, int year)->double{ return money * rate * year;
};// 等效的函数对象
class Rate {
public:Rate(double rate) : _rate(rate) {}double operator()(double money, int year) { return money * _rate * year; }
private:double _rate;
};
11. 包装器
11.1 function包装器
可调用对象以及缺点:
-
函数指针:定义复杂
-
仿函数对象:要定义一个类,用的时候有点麻烦,不适合统一类型
-
lambda: 没有类型概念
上述可调用对象形式不同,但都有可以被调用的功能,因此C++11引入包装器,可以用于可调用对象的类型统一。
function包装器作用:统一可调用对象的类型,不是定义!是已有对象进行“包装”
在库中的原型:

可以粗略的任务包装器就是一个适配器,将我们传入的函数对象进行适配,他在底层也是重载operator()来进行调用。
但是他的语法与我们平时使用的略有不同,他的模板参数不用逗号分隔,而是加入括号,就像原型定义的一样Ret(Args…),Ret为返回值类型,参数包为参数类型包,括号也必须带上,示例如下:
#include <functional>// 函数指针
std::function<int(int, int)> func1 = f;
// 函数对象
std::function<int(int, int)> func2 = Functor();
// lambda表达式
std::function<int(int, int)> func3 = [](int a, int b){ return a + b; };
class plus{
public:
int plusi
};// 类成员函数
std::function<int(int, int)> func4 = &Plus::plusi;
std::function<double(Plus*, double, double)> func5 = &Plus::plusd;
std::function<double(Plus, double, double)> func6 = &Plus::plusd;
//调用:
func5(&plus, 1.1, 2.2); // &puls->plusd(1.1,2.2);
func6(plus(), 1.1, 2.2); // puls.plusd(1.1,2.2);
静态成员函数包装和普通的函数相同,但是需要指定类域。
普通成员函数包装规定在指定类域后还需要加上取地址符(静态不用加,但我们建议加上,这样可以更加统一),类成员函数在调用的时候还需要对象的引用或者地址,因此在普通类成员的时候要带上类对象的引用或者指针。
11.2 bind绑定器
bind绑定器是一个函数模板,接受一个可调用对象生成新的可调用对象
用来调整可调用对象的参数个数或者调用顺序

作用:生成新的可调用对象,适应原函数的参数列表
#include <functional>int Plus(int a, int b) { return a + b; }class Sub {
public:int sub(int a, int b) { return a - b; }
};int main() {// 绑定普通函数auto func1 = std::bind(Plus, std::placeholders::_1, std::placeholders::_2);// 绑定固定参数auto func2 = std::bind(Plus, 1, 2);// 绑定成员函数Sub s;auto func3 = std::bind(&Sub::sub, s, std::placeholders::_1, std::placeholders::_2);// 参数调换顺序auto func4 = std::bind(&Sub::sub, s, std::placeholders::_2, std::placeholders::_1);
}
12. 线程库
C++线程库是对于底层平台线程库的封装,这里只做多线程库的使用介绍,并不过多解释多线程的知识,如果对多线程的知识感兴趣,可以我的操作系统参考文章:多线程初识,多线程控制,线程同步与互斥
12.1 thread类基本使用
创建线程的三种方式:
#include <thread>// 1. 函数指针
void ThreadFunc(int a) {cout << "Thread1" << a << endl;
}// 2. lambda表达式
auto lambda = []{ cout << "Thread2" << endl; };// 3. 函数对象
class TF {
public:void operator()() {cout << "Thread3" << endl;}
};int main() {thread t1(ThreadFunc, 10); // 函数指针thread t2(lambda); // lambda表达式thread t3(TF()); // 函数对象t1.join();t2.join(); t3.join();
}
12.2 线程参数传递
void ThreadFunc1(int& x) { x += 10; }
void ThreadFunc2(int* x) { *x += 10; }int main() {int a = 10;thread t1(ThreadFunc1, a); // 值传递,不影响外部实参thread t2(ThreadFunc1, std::ref(a)); // 引用传递,影响外部实参thread t3(ThreadFunc2, &a); // 指针传递,影响外部实参
}
12.3 原子操作
解决多线程数据竞争:
#include <atomic>// 传统方式(需要加锁)
std::mutex m;
unsigned long sum = 0L;
void fun(size_t num) {for (size_t i = 0; i < num; ++i) {m.lock();sum++;m.unlock();}
}// C++11原子操作
std::atomic_long sum{0};
void fun(size_t num) {for (size_t i = 0; i < num; ++i) {sum++; // 原子操作,无需加锁}
}
12.4 锁管理
lock_guard:简单的RAII锁管理
std::mutex mtx;
{std::lock_guard<std::mutex> lock(mtx); // 构造时上锁// 临界区代码
} // 析构时自动解锁
unique_lock:更灵活的锁管理
std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx);
// 支持手动操作
lock.unlock();
// ... 其他操作
lock.lock();
12.5 条件变量
#include <thread>
#include <mutex>
#include <condition_variable>void two_thread_print() {std::mutex mtx;std::condition_variable cv;int n = 100;bool flag = true;// 打印偶数thread t1([&]() {for (int i = 0; i < n; i += 2) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [&](){ return flag; });cout << i << endl;flag = false;cv.notify_one();}});// 打印奇数 thread t2([&]() {for (int j = 1; j < n; j += 2) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [&](){ return !flag; });cout << j << endl;flag = true;cv.notify_one();}});t1.join();t2.join();
}
