C++11是继C++03之后的一次大更新,出现了很多不同的语法,这里来逐一学习一下吧。
统一的列表初始化
C++11实现了初始化的大一统,无论是内置类型还是自定义类型都可以通过{}来进行初始化。
内置类型初始化
先来观测下列代码:
cpp
int main()
{
int x = { 10 };//列表初始化
int y{ 15 };//忽略'='的列表初始化
char ch{ 'c' };
cout << x << endl << y << endl << ch << endl;
return 0;
}
运行结果:
10
15
c
上述结果表明,对于内置类型可以以= { 参数 } 或 { 参数 }的形式进行初始化。
自定义类型
先以STL自带的容器进行试验:
cpp
int main()
{
string str1{ "Love" };
cout << str1 << endl;
return 0;
}
输出结果为:
Love
实际上本质是参数的隐式类型转换,不相信的话我们来以简单的自定义类型试验一下。
先定义一个简单的Point类型:
cpp
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。
如果像下面这种:
cpp
explicit Point(const int x,const int y)
:_x(x)
,_y(y)
{
cout << "构造" << endl;
}
error C3445: "Point" 的复制列表初始化不能使用显式构造函数
或者
cpp
Point& w = 2;
error C2440: "初始化": 无法从"int"转换为"Point &"
initializer_list初始化
之前写vector的区间构造也提到过,我们可以以下列方式来初始化vector的一堆值:
cpp
vector<int>v = { 1,2,3 };
这种形式和上述的隐式类型转换相似又不一致,确实是调用了vector的构造函数,具体是:

没错,这里其实是传入了迭代器 来进行初始化。
C++11中将{}封装成了一个类:
cpp
auto x = { 1,2,3 };
cout << typeid(x).name() << endl;
结果:
class std::initializer_list<int>
也就是说所有花括号都是initializer_list这个类,上面vector的初始化,实际上就是传入initializer_list的迭代器来实现的。
具体应用
有了这些语法,我们能实现部分容器的高效初始化:
cpp
map<string, int>m = {
{"apple",19},
{"banana",10},
{"Love",0}
};
可以看到,这里其实是首先从{"apple",19}隐式类型转换为pair<string,int>,然后再根据initializer_list初始化到m上。
声明
C++11增加了许多简化声明的方式。
auto
auto关键字前面已经讲过了,可以自动推导类型。像:
cpp
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还可以当作返回值类型:
cpp
auto minus(int x, int y)
{
return x - y;
}
int main()
{
cout << ::minus(10, 6) << endl;
return 0;
}
结果:
4
当然了,auto不能作参数列表。
cpp
int times(int x,auto y)
{
return x * y;
}
error C3533: 参数不能为包含"auto"的类型
事实上我们有模板功能来实现接入参数的类型随机性,也不需要auto来实现。
decltype
decltype顾名思义就是declaim type,获取类型。不同于typied.name()得到的仅是字符串,decltype得到的类型名是可以直接当作类型使用的:
cpp
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的类型是double
decltype(&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的例子:
cpp
vector<int> v = { 1,2,3,4,5 };
for (auto& e : v)
{
cout << e << ' ';
}
cout << endl;
相当于:
cpp
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中新增的相当重要的语法。
左值和右值
首先需要明确何为左值何为右值?
简单来说,左值就是可以取地址的数值表达式,右值就是不能取地址的数值表达式
如:
cpp
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这些都是左值。
常见的右值有:字面常量、表达式返回值、函数返回值
cpp
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明确表明了,只有左值才能取地址。
不过这里要郑重申明,右值只是不能取地址,不代表没有地址
左值引用和右值引用
左值引用自然就是对左值的引用,右值引用就是对右值的引用:
左值引用
cpp
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
右值引用
cpp
int&& rr1 = 10;
int&& rr2 = 2 - 1;
int&& rr3 = add(2, 10);
可以看到,右值引用是类型名+"&&"
接下来我们需要考虑两件事情,那就是左值引用能否引用右值,右值引用能否引用左值?
cpp
int& rr1 = 10+2;
int x = 20;
int&& y = x;
error C2440: "初始化": 无法从"int"转换为"int &"
error C2440: "初始化": 无法从"int"转换为"int &&"
可以看到左值引用直接引用右值和右值引用直接引用左值都会报错,那是否没有办法了,自然不是。
cpp
const int& rr1 = 10 + 2;
int x = 20;
int&& y = move(x);
像这种形式就可以通过编译了。也就是说const 左值引用可以引用左值也可以引用右值,右值引用只能应用右值或者move以后的左值。
这里的move函数实际上就是将左值转换成右值。
- 还有一点比较重要的。我们都知道左值引用本身也是左值,那右值引用是左值还是右值呢?
cpp
int main()
{
int&& x = 10;
cout << &x << endl;
return 0;
}
000000CE6D8FF614
可以看到,右值引用本身也是左值。同时也证明了右值确实也有地址。
总结:
- 左值引用只能引用左值,不能引用右值。
- 但是const左值引用既可引用左值,也可引用右值。
- 右值引用只能右值,不能引用左值。
- 但是右值引用可以move以后的左值。
移动构造和移动赋值
右值其实又细分为纯右值和将亡值。纯右值是指字面常量那些,将亡值则是指临时变量或传值返回。
基于将亡值即将销毁的特性,我们可以利用其资源来完成构造和拷贝:
cpp
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的实现,我们来观测一下下面情况:
cpp
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.
当然两次拷贝构造被编译器优化成了一次,如果是下面这样写的话,优化更甚:
cpp
mystring::string ret2 = mystring::string::to_string(1234);
cout << ret2.c_str() << endl;
string(char* str) -- 构造
1234
可以看到,他竟然优化成了一次构造,不得不说现在的编译器是真的厉害啊。
回到原文,考虑一开始的情况。可以发现to_string返回的是值,对他进行深拷贝然后再深拷贝给ret1代价无疑非常大。如果我们实现了移动构造,那么代价就可以缩小:
cpp
mystring::string ret1;
ret1 = mystring::string::to_string(1234);
string(char* str) -- 构造
string(char* str) -- 构造
string(string&& s) -- 移动赋值
可以看到拷贝构造变成了赋值构造,代价大大降低了。
当然了,我们可不要做一个喜新厌旧的人。移动拷贝并非所有情况都是最优的。我们之所以用移动构造代替拷贝构造能提升效率,本质上拷贝构造调的是深拷贝,如果是浅拷贝的话拷贝构造效率自然会比移动构造高。
完美转发
前面提到过,右值引用本身会退化成左值,这就为某些事请埋下了伏笔。
且看下面函数:
cpp
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函数变成了全部都是左值函数了。
当然了我们可以强制类型转换一下:
cpp
template<typename T>
void PerfectForward(T&& t)
{
Fun((T&&)t);
}
右值引用
左值引用
右值引用
const 左值引用
const 右值引用
结果自然正确了,但这不是C++11提供的最佳方案。C++11提供了完美转发函数forward能完美传递对应的左右值类型,使用方法如下:
cpp
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 }
cpp
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
可以看到,返回值类型可以自动推导因此可以省略。如果没有参数,甚至可以省略参数列表。
应用
cpp
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的函数,还是非常方便的。
捕捉列表
捕捉列表,可以捕捉当前作用域的参数
cpp
int main()
{
int a = 10;
auto func = [a] {cout << a << endl; };
func();
return 0;
}
10
不过这里默认是不可修改捕捉值的:
cpp
auto func = [a] {a = 19; };
"a": 无法在非可变 lambda 中修改通过复制捕获
需要加上mutable关键字
cpp
int main()
{
int a = 10;
auto func = [a]()mutable {a = 19; cout << a << endl; };
func();
cout << a << endl;
return 0;
}
19
10
可以看到这并没有多大的意义,毕竟是传值捕捉。如果想要修改对应作用域的值,需要引用捕捉。
cpp
int main()
{
int a = 10;
auto func = [&a](){a = 19; cout << a << endl; };
func();
cout << a << endl;
return 0;
}
19
19
此外=表示传值捕捉当前作用域所有变量,\&表示引用捕捉当前作用域所有变量,当然还可以混合捕捉,具体看下面代码:
cpp
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允许你创建可以接受可变参数的函数模板和类模板,具体格式如下:
cpp
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
可以看到可变参数模板其实有一个相当晦涩的语法表达式。
那么接下来有个严肃的问题,既然有了参数包,要怎么获取其中的参数呢?相信很多人第一时间想到了下列方案:
cpp
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": 必须在此上下文中扩展参数包
事实上我也不是很理解为什么这样是不行的,但总之就是不行
正确的方法有两种
- 以递归形式拆解拓展包
cpp
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
当然了,如果你觉得这种展开方式过于晦涩,我们还有更晦涩的另一种方式:
- 逗号表达式展开
cpp
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没有区别。
例如:
cpp
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还可以这样调用:
cpp
v.emplace_back("love u",10000);
这个参数包会层层下传,最后只调用一次构造。
因此总的来说emplace系列比push和insert系列效率是要高的。
包装器
C++11中可以起到调用函数功能的有:函数指针、仿函数、lambda表达式。类型混乱无章,无法统一调配。因此需要对他们包装一下,至少使得他们的外表是同一个类,也就是所谓的包装器。
function包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
先看使用格式:
cpp
std::function在头文件<functional>
// 类模板原型如下
template <class T> function;
// undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args...:被调用函数的形参
可以看到,这里的模板显示实例化和一般格式不相同,但其实更方便理解,在()前的是函数返回值类型,括号中间则是返回值列表。
那么先来简单使用一下吧:
cpp
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还有一些有实际意义的使用场景:
cpp
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包装器。能够调整参数顺序和个数,具体使用格式:
cpp
// 原型如下:
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);
具体使用:
cpp
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函数的各种绑定情况:
cpp
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() | 在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关 |
先来看看简单的使用:
cpp
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上锁,现在上锁改进下.
cpp
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就一直锁着,就会导致程序死锁。
因此前辈考虑到用类的构造和析构函数形式来上锁和解锁:
cpp
class LockGuard
{
public:
LockGuard(mutex& mtx)
:_mtx(mtx)
{
_mtx.lock();
}
~LockGuard()
{
_mtx.unlock();
}
private:
mutex& _mtx;
};
类似这般使用:
cpp
//局部域
{
LockGuard lock(mtx);
lock_guard<mutex> lock(mtx);
for (size_t i = 0; i < n; i++)
{
++x;
}
}
那么有了线程库自然能实现常见的代码:两个线程交替打印奇偶数,
cpp
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;
}
各种奥秘,请读者自行参悟.