学习笔记:C++ 11 新特性
C++ 11 新特性之auto 和decltype 知识点
auto
auto a = 10; // 10是int型,可以自动推导出a是int
int i = 10;auto b = i; // b是int型
auto d = 2.0; // d是double型
这就是
auto 推导规则
直接看代码
代码
int i = 10;
auto a = i, &b = i, *c = &i; // a是int,b是i的引用,c是i的指针,auto就相当于int
auto d = 0, f = 1.0; // error,0和1.0类型不同,对于编译器有二义性,没法推导
auto e; // error,使用auto必须马上初始化,否则无法推导类型
代码
void func(auto value) {} // error,auto不能用作函数参数
class A {
auto a = 1; // error,在类中auto不能用作非静态成员变量
static auto b = 1; // error,这里与auto无关,正常static int b = 1也不可以
static const auto int c = 1; // ok
};
void func2() {
int a[10] = {0};
auto b = a; // ok
auto c[10] = a; // error,auto不能定义数组,可以定义指针
vector<int> d;
vector<auto> f = d; // error,auto无法推导出模板参数
}
-
auto 的使用必须马上初始化,否则无法推导出类型 -
auto 在一行定义多个变量时,各个变量的推导不能产生二义性,否则编译失败 -
auto 不能用作函数参数 -
在类中
auto 不能用作非静态成员变量 -
auto 不能定义数组,可以定义指针 -
auto 无法推导出模板参数
再看这段代码:
int i = 0;
auto *a = &i; // a是int*
auto &b = i; // b是int&
auto c = b; // c是int,忽略了引用
const auto d = i; // d是const int
auto e = d; // e是int
const auto& f = e; // f是const int&
auto &g = f; // g是const int&
首先,介绍下,这里的
推导规则
- 在不声明为引用或指针时,
auto 会忽略等号右边的引用类型和cv 限定 - 在声明为引用或者指针时,
auto 会保留等号右边的引用和cv 属性
什么时候使用auto ?
这里没有绝对答案,在不影响代码代码可读性的前提下尽可能使用
auto func = [&] {
cout << "xxx";
}; // 对于func难道不使用auto吗,反正是不关心lambda表达式究竟是什么类型。
auto asyncfunc = std::async(std::launch::async, func);
// 对于asyncfunc难道不使用auto吗,懒得写std::futurexxx等代码,而且也记不住它返回的究竟是什么...
decltype
上面介绍
int func() { return 0; }
decltype(func()) i; // i为int类型
int x = 0;
decltype(x) y; // y是int类型
decltype(x + y) z; // z是int类型
注意:
cont int &i = 1;
int a = 2;
decltype(i) b = 2; // b是const int&
decltype 推导规则
对于
-
exp 是表达式,decltype(exp) 和exp 类型相同 -
exp 是函数调用,decltype(exp) 和函数返回值类型相同 -
其它情况,若
exp 是左值,decltype(exp) 是exp 类型的左值引用
int a = 0, b = 0;
decltype(a + b) c = 0; // c是int,因为(a+b)返回一个右值
decltype(a += b) d = c;// d是int&,因为(a+=b)返回一个左值
d = 20;
cout << "c " << c << endl; // 输出c 20
auto 和decltype 的配合使用
下面这段代码
template<typename T, typename U>
return_value add(T t, U u) { // t和v类型不确定,无法推导出return_value类型
return t + u;
}
上面代码由于
template<typename T, typename U>
decltype(t + u) add(T t, U u) { // t和u尚未定义
return t + u;
}
这段代码在
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
return t + u;
}
返回值后置类型语法就是为了解决函数返回值类型依赖于参数但却难以确定返回值类型的问题。
C++11 新特性之左值引用、右值引用、移动语义、完美转发
-
左值
-
右值
-
纯右值
-
将亡值
-
左值引用
-
右值引用
-
移动语义
-
完美转发
-
返回值优化
左值、右值
概念
左值:可以放到等号左边的东西叫左值。
右值:不可以放到等号左边的东西就叫右值。
概念
左值:可以取地址并且有名字的东西就是左值。
右值:不能取地址的没有名字的东西就是右值。
举例:
int a = b + c;
int a = 4; // a是左值,4作为普通字面量是右值
左值一般有:
-
函数名和变量名
-
返回左值引用的函数调用
-
前置自增自减表达式
++i 、–i -
由赋值表达式或赋值运算符连接的表达式
(a=b, a += b 等) -
解引用表达式
*p -
字符串字面值
"abcd"
纯右值、将亡值
纯右值和将亡值都属于右值。
纯右值
运算表达式产生的临时变量、不和对象关联的原始字面量、非引用返回的临时变量、
举例:
-
除字符串字面值外的字面值
-
返回非引用类型的函数调用
-
后置自增自减表达式
i++ 、i– -
算术表达式
(a+b, a*b, a&&b, a==b 等) -
取地址表达式等
(&a)
将亡值
将亡值是指
举例:
class A {
xxx;
};
A a;
auto c = std::move(a); // c是将亡值
auto d = static_cast<A&&>(a); // d是将亡值
左值引用、右值引用
根据名字大概就可以猜到意思,左值引用就是对左值进行引用的类型,右值引用就是对右值进行引用的类型,他们都是引用,都是对象的一个别名,并不拥有所绑定对象的堆存,所以都必须立即初始化。
type &name = exp; // 左值引用
type &&name = exp; // 右值引用
左值引用
看代码:
int a = 5;
int &b = a; // b是左值引用
b = 4;
int &c = 10; // error,10无法取地址,无法进行引用
const int &d = 10; // ok,因为是常引用,引用常量数字,这个常量数字会存储在内存中,可以取地址
可以得出结论:对于左值引用,等号右边的值必须可以取地址,如果不能取地址,则会编译失败,或者可以使用
右值引用
如果使用右值引用,那表达式等号右边的值需要时右值,可以使用
int a = 4;
int &&b = a; // error, a是左值
int &&c = std::move(a); // ok
移动语义
谈移动语义前,首先需要了解深拷贝与浅拷贝的概念
深拷贝、浅拷贝
直接拿代码举例
class A {
public:
A(int size) : size_(size) {
data_ = new int[size];
}
A(){}
A(const A& a) {
size_ = a.size_;
data_ = a.data_;
cout << "copy " << endl;
}
~A() {
delete[] data_;
}
int *data_;
int size_;
};
int main() {
A a(10);
A b = a;
cout << "b " << b.data_ << endl;
cout << "a " << a.data_ << endl;
return 0;
}
上面代码中,两个输出的是相同的地址,
class A {
public:
A(int size) : size_(size) {
data_ = new int[size];
}
A(){}
A(const A& a) {
size_ = a.size_;
data_ = new int[size_];
cout << "copy " << endl;
}
~A() {
delete[] data_;
}
int *data_;
int size_;
};
int main() {
A a(10);
A b = a;
cout << "b " << b.data_ << endl;
cout << "a " << a.data_ << endl;
return 0;
}
深拷贝就是再拷贝对象时,如果被拷贝对象内部还有指针引用指向其它资源,自己需要重新开辟一块新内存存储资源,而不是简单的赋值。
移动语义可以理解为转移所有权,之前的拷贝是对于别人的资源,自己重新分配一块内存存储复制过来的资源,而对于移动语义,类似于转让或者资源窃取的意思,对于那块资源,转为自己所拥有,别人不再拥有也不会再使用,通过
class A {
public:
A(int size) : size_(size) {
data_ = new int[size];
}
A(){}
A(const A& a) {
size_ = a.size_;
data_ = new int[size_];
cout << "copy " << endl;
}
A(A&& a) {
this->data_ = a.data_;
a.data_ = nullptr;
cout << "move " << endl;
}
~A() {
if (data_ != nullptr) {
delete[] data_;
}
}
int *data_;
int size_;
};
int main() {
A a(10);
A b = a;
A c = std::move(a); // 调用移动构造函数
return 0;
}
如果不使用
std::vector<string> vecs;
...
std::vector<string> vecm = std::move(vecs); // 免去很多拷贝
注意:移动语义仅针对于那些实现了移动构造函数的类的对象,对于那种基本类型
完美转发
完美转发指可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参,转发函数实参是左值那目标函数实参也是左值,转发函数实参是右值那目标函数实参也是右值。那如何实现完美转发呢,答案是使用
void PrintV(int &t) {
cout << "lvalue" << endl;
}
void PrintV(int &&t) {
cout << "rvalue" << endl;
}
template<typename T>
void Test(T &&t) {
PrintV(t);
PrintV(std::forward<T>(t));
PrintV(std::move(t));
}
int main() {
Test(1); // lvalue rvalue rvalue
int a = 1;
Test(a); // lvalue lvalue rvalue
Test(std::forward<int>(a)); // lvalue rvalue rvalue
Test(std::forward<int&>(a)); // lvalue lvalue rvalue
Test(std::forward<int&&>(a)); // lvalue rvalue rvalue
return 0;
}
分析
-
Test(1):
1 是右值,模板中T &&t 这种为万能引用,右值1 传到Test 函数中变成了右值引用,但是调用PrintV() 时候,t 变成了左值,因为它变成了一个拥有名字的变量,所以打印lvalue ,而PrintV(std::forward (t)) 时候,会进行完美转发,按照原来的类型转发,所以打印rvalue ,PrintV(std::move(t)) 毫无疑问会打印rvalue 。 -
Test(a):
a 是左值,模板中T && 这种为万能引用,左值a 传到Test 函数中变成了左值引用,所以有代码中打印。 -
Test(std::forward
(a)):转发为左值还是右值,依赖于 T ,T 是左值那就转发为左值,T 是右值那就转发为右值。
返回值优化
返回值优化
那什么时候编译器会进行返回值优化呢
return 的值类型与函数的返回值类型相同return 的是一个局部对象
看几个例子
示例
std::vector<int> return_vector(void) {
std::vector<int> tmp {1,2,3,4,5};
return tmp;
}
std::vector<int> &&rval_ref = return_vector();
不会触发
const std::vector<int>& rval_ref = return_vector();
示例
std::vector<int>&& return_vector(void) {
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
这段代码会造成运行时错误,因为
示例
std::vector<int> return_vector(void) {
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
和示例
最好的代码:
std::vector<int> return_vector(void) {
std::vector<int> tmp {1,2,3,4,5};
return tmp;
}
std::vector<int> rval_ref = return_vector();
这段代码会触发
C++11 新特性之列表初始化
在
struct A {
public:
A(int) {}
private:
A(const A&) {}
};
int main() {
A a(123);
A b = 123; // error
A c = { 123 };
A d{123}; // c++11
int e = {123};
int f{123}; // c++11
return 0;
}
列表初始化也可以用在函数的返回值上
std::vector<int> func() {
return {};
}
列表初始化的一些规则
首先说下聚合类型可以进行直接列表初始化,这里需要了解什么是聚合类型:
- 类型是一个普通数组,如
int[5] ,char[],double[] 等 - 类型是一个类,且满足以下条件:
-
- 没有用户声明的构造函数
- 没有用户提供的构造函数
( 允许显示预置或弃置的构造函数)
-
- 没有私有或保护的非静态数据成员
- 没有基类
-
- 没有虚函数
- 没有
{} 和= 直接初始化的非静态数据成员
-
- 没有默认成员初始化器
struct A {
int a;
int b;
int c;
A(int, int){}
};
int main() {
A a{1, 2, 3};// error,A有自定义的构造函数,不能列表初始化
}
上述代码类
struct A {
int a;
int b;
virtual void func() {} // 含有虚函数,不是聚合类
};
struct Base {};
struct B : public Base { // 有基类,不是聚合类
int a;
int b;
};
struct C {
int a;
int b = 10; // 有等号初始化,不是聚合类
};
struct D {
int a;
int b;
private:
int c; // 含有私有的非静态数据成员,不是聚合类
};
struct E {
int a;
int b;
E() : a(0), b(0) {} // 含有默认成员初始化器,不是聚合类
};
上面列举了一些不是聚合类的例子,对于一个聚合类型,使用列表初始化相当于对其中的每个元素分别赋值;对于非聚合类型,需要先自定义一个对应的构造函数,此时列表初始化将调用相应的构造函数。
std::initializer_list
平时开发使用
struct CustomVec {
std::vector<int> data;
CustomVec(std::initializer_list<int> list) {
for (auto iter = list.begin(); iter != list.end(); ++iter) {
data.push_back(*iter);
}
}
};
这个
注意:std::initializer_list
列表初始化的好处
列表初始化的好处如下:
-
方便,且基本上可以替代括号初始化
-
可以使用初始化列表接受任意长度
-
可以防止类型窄化,避免精度丢失的隐式类型转换
什么是类型窄化,列表初始化通过禁止下列转换,对隐式转化加以限制:
-
从浮点类型到整数类型的转换
-
从
long double 到double 或float 的转换,以及从double 到float 的转换,除非源是常量表达式且不发生溢出 -
从整数类型到浮点类型的转换,除非源是其值能完全存储于目标类型的常量表达式
-
从整数或无作用域枚举类型到不能表示原类型所有值的整数类型的转换,除非源是其值能完全存储于目标类型的常量表达式
示例:
int main() {
int a = 1.2; // ok
int b = {1.2}; // error
float c = 1e70; // ok
float d = {1e70}; // error
float e = (unsigned long long)-1; // ok
float f = {(unsigned long long)-1}; // error
float g = (unsigned long long)1; // ok
float h = {(unsigned long long)1}; // ok
const int i = 1000;
const int j = 2;
char k = i; // ok
char l = {i}; // error
char m = j; // ok
char m = {j}; // ok,因为是const类型,这里如果去掉const属性,也会报错
}
打印如下:
test.cc:24:17: error: narrowing conversion of ‘1.2e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
int b = {1.2};
^
test.cc:27:20: error: narrowing conversion of ‘1.0000000000000001e+70’ from ‘double’ to ‘float’ inside { } [-Wnarrowing]
float d = {1e70};
test.cc:30:38: error: narrowing conversion of ‘18446744073709551615’ from ‘long long unsigned int’ to ‘float’ inside { } [-Wnarrowing]
float f = {(unsigned long long)-1};
^
test.cc:36:14: warning: overflow in implicit constant conversion [-Woverflow]
char k = i;
^
test.cc:37:16: error: narrowing conversion of ‘1000’ from ‘int’ to ‘char’ inside { } [-Wnarrowing]
char l = {i};
C++11 新特性std::function 和lambda 表达式
std::function
、std::bind
、lambda
表达式等封装使函数调用更加方便。
std::function
讲std::function
前首先需要了解下什么是可调用对象
满足以下条件之一就可称为可调用对象:
-
是一个函数指针
-
是一个具有
operator()
成员函数的类对象( 传说中的仿函数) ,lambda 表达式 -
是一个可被转换为函数指针的类对象
-
是一个类成员
( 函数) 指针 -
bind 表达式或其它函数对象
而std::function
就是上面这种可调用对象的封装器,可以把std::function
看做一个函数对象,用于表示函数这个抽象概念。std::function
的实例可以存储、复制和调用任何可调用对象,存储的可调用对象称为std::function
的目标,若std::function
不含目标,则称它为空,调用空的std::function
的目标会抛出std::bad_function_call
异常。
使用参考如下实例代码:
std::function<void(int)> f; // 这里表示function的对象f的参数是int,返回值是void
#include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_ + i << '\n'; }
int num_;
};
void print_num(int i) { std::cout << i << '\n'; }
struct PrintNum {
void operator()(int i) const { std::cout << i << '\n'; }
};
int main() {
// 存储自由函数
std::function<void(int)> f_display = print_num;
f_display(-9);
// 存储 lambda
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
// 存储到 std::bind 调用的结果
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();
// 存储到成员函数的调用
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);
f_add_display(314159, 1);
// 存储到数据成员访问器的调用
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n';
// 存储到成员函数及对象的调用
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);
f_add_display2(2);
// 存储到成员函数和对象指针的调用
std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);
f_add_display3(3);
// 存储到函数对象的调用
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);
}
从上面可以看到std::function
的使用方法,当给std::function
填入合适的参数表和返回值后,它就变成了可以容纳所有这一类调用方式的函数封装器。std::function
还可以用作回调函数,或者在std::function
,特别方便。
std::bind
使用std::bind
可以将可调用对象和参数一起绑定,绑定后的结果使用std::function
进行保存,并延迟调用到任何需要的时候。
std::bind
通常有两大作用:
- 将可调用对象与参数一起绑定为另一个
std::function
供调用 - 将
n 元可调用对象转成m(m < n) 元可调用对象,绑定一部分参数,这里需要使用std::placeholders
具体示例:
#include <functional>
#include <iostream>
#include <memory>
void f(int n1, int n2, int n3, const int& n4, int n5) {
std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << std::endl;
}
int g(int n1) { return n1; }
struct Foo {
void print_sum(int n1, int n2) { std::cout << n1 + n2 << std::endl; }
int data = 10;
};
int main() {
using namespace std::placeholders; // 针对 _1, _2, _3...
// 演示参数重排序和按引用传递
int n = 7;
// ( _1 与 _2 来自 std::placeholders ,并表示将来会传递给 f1 的参数)
auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
n = 10;
f1(1, 2, 1001); // 1 为 _1 所绑定, 2 为 _2 所绑定,不使用 1001
// 进行到 f(2, 42, 1, n, 7) 的调用
// 嵌套 bind 子表达式共享占位符
auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
f2(10, 11, 12); // 进行到 f(12, g(12), 12, 4, 5); 的调用
// 绑定指向成员函数指针
Foo foo;
auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1);
f3(5);
// 绑定指向数据成员指针
auto f4 = std::bind(&Foo::data, _1);
std::cout << f4(foo) << std::endl;
// 智能指针亦能用于调用被引用对象的成员
std::cout << f4(std::make_shared<Foo>(foo)) << std::endl;
}
lambda
表达式
auto func = [capture] (params) opt -> ret { func_body; };
其中func
是可以当作lambda
表达式的名字,作为一个函数使用,capture
是捕获列表,params
是参数表,opt
是函数选项
一个完整的
auto func1 = [](int a) -> int { return a + 1; };
auto func2 = [](int a) { return a + 2; };
cout << func1(1) << " " << func2(2) << endl;
如上代码,很多时候
lambda
表达式允许捕获一定范围内的变量:
-
[]
不捕获任何变量 -
[&]
引用捕获,捕获外部作用域所有变量,在函数体内当作引用使用 -
[=]
值捕获,捕获外部作用域所有变量,在函数内内有个副本使用 -
[=, &a]
值捕获外部作用域所有变量,按引用捕获a 变量 -
[a]
只值捕获a 变量,不捕获其它变量 -
[this]
捕获当前类中的this 指针
int a = 0;
auto f1 = [=](){ return a; }; // 值捕获a
cout << f1() << endl;
auto f2 = [=]() { return a++; }; // 修改按值捕获的外部变量,error
auto f3 = [=]() mutable { return a++; };
代码中的operator()
成员函数的类对象,这个operator()
默认是const
的,所以不能修改成员变量,而加了mutable
,就是去掉const
属性。
还可以使用
struct A {
int a;
int b;
};
int main() {
vector<A> vec;
std::sort(vec.begin(), vec.end(), [](const A &left, const A &right) { return left.a < right.a; });
}
总结
std::function
和std::bind
在平时编程过程中封装函数更加的方便,而
C++11 新特性之模板改进
-
模板的右尖括号
-
模板的别名
-
函数模板的默认模板参数
模板的右尖括号
模板的别名
int main() {
std::vector<std::vector<int>> a; // error
std::vector<std::vector<int> > b; // ok
}
使用
typedef void (*func)(int, int);
using func = void (*)(int, int); // 起码比typedef容易看的懂
上面的代码使用
函数模板的默认模板参数
template <typename T, typename U=int>
class A {
T value;
};
template <typename T=int, typename U> // error
class A {
T value;
};
类模板的默认模板参数必须从右往左定义,而函数模板则没有这个限制。
template <typename R, typename U=int>
R func1(U val) {
return val;
}
template <typename R=int, typename U>
R func2(U val) {
return val;
}
int main() {
cout << func1<int, double>(99.9) << endl; // 99
cout << func1<double, double>(99.9) << endl; // 99.9
cout << func1<double>(99.9) << endl; // 99.9
cout << func1<int>(99.9) << endl; // 99
cout << func2<int, double>(99.9) << endl; // 99
cout << func1<double, double>(99.9) << endl; // 99.9
cout << func2<double>(99.9) << endl; // 99.9
cout << func2<int>(99.9) << endl; // 99
return 0;
}
C++11 新特性之线程相关知识点
-
std::thread 相关 -
std::mutex 相关 -
std::lock 相关 -
std::atomic 相关 -
std::call_once 相关 -
volatile 相关 -
std::condition_variable 相关 -
std::future 相关 -
async 相关
std::thread 相关
#include <iostream>
#include <thread>
using namespace std;
int main() {
auto func = []() {
for (int i = 0; i < 10; ++i) {
cout << i << " ";
}
cout << endl;
};
std::thread t(func);
if (t.joinable()) {
t.detach();
}
auto func1 = [](int k) {
for (int i = 0; i < k; ++i) {
cout << i << " ";
}
cout << endl;
};
std::thread tt(func1, 20);
if (tt.joinable()) { // 检查线程可否被join
tt.join();
}
return 0;
}
上述代码中,函数
如果没有调用
这里可以对
class ThreadGuard {
public:
enum class DesAction { join, detach };
ThreadGuard(std::thread&& t, DesAction a) : t_(std::move(t)), action_(a){};
~ThreadGuard() {
if (t_.joinable()) {
if (action_ == DesAction::join) {
t_.join();
} else {
t_.detach();
}
}
}
ThreadGuard(ThreadGuard&&) = default;
ThreadGuard& operator=(ThreadGuard&&) = default;
std::thread& get() { return t_; }
private:
std::thread t_;
DesAction action_;
};
int main() {
ThreadGuard t(std::thread([]() {
for (int i = 0; i < 10; ++i) {
std::cout << "thread guard " << i << " ";
}
std::cout << std::endl;}), ThreadGuard::DesAction::join);
return 0;
}
std::thread t(func);
cout << "当前线程ID " << t.get_id() << endl;
cout << "当前cpu个数 " << std::thread::hardware_concurrency() << endl;
auto handle = t.native_handle();// handle可用于pthread相关操作
std::this_thread::sleep_for(std::chrono::seconds(1));
std::mutex 相关
-
std::mutex:独占的互斥量,不能递归使用,不带超时功能
-
std::recursive_mutex:递归互斥量,可重入,不带超时功能
-
std::timed_mutex:带超时的互斥量,不能递归
-
std::recursive_timed_mutex:带超时的互斥量,可以递归使用
拿一个
std::mutex:
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
std::mutex mutex_;
int main() {
auto func1 = [](int k) {
mutex_.lock();
for (int i = 0; i < k; ++i) {
cout << i << " ";
}
cout << endl;
mutex_.unlock();
};
std::thread threads[5];
for (int i = 0; i < 5; ++i) {
threads[i] = std::thread(func1, 200);
}
for (auto& th : threads) {
th.join();
}
return 0;
}
std::timed_mutex:
#include <iostream>
#include <mutex>
#include <thread>
#include <chrono>
using namespace std;
std::timed_mutex timed_mutex_;
int main() {
auto func1 = [](int k) {
timed_mutex_.try_lock_for(std::chrono::milliseconds(200));
for (int i = 0; i < k; ++i) {
cout << i << " ";
}
cout << endl;
timed_mutex_.unlock();
};
std::thread threads[5];
for (int i = 0; i < 5; ++i) {
threads[i] = std::thread(func1, 200);
}
for (auto& th : threads) {
th.join();
}
return 0;
}
std::lock 相关
这里主要介绍两种
#include <iostream>
#include <mutex>
#include <thread>
#include <chrono>
using namespace std;
std::mutex mutex_;
int main() {
auto func1 = [](int k) {
// std::lock_guard<std::mutex> lock(mutex_);
std::unique_lock<std::mutex> lock(mutex_);
for (int i = 0; i < k; ++i) {
cout << i << " ";
}
cout << endl;
};
std::thread threads[5];
for (int i = 0; i < 5; ++i) {
threads[i] = std::thread(func1, 200);
}
for (auto& th : threads) {
th.join();
}
return 0;
}
std::atomic 相关
struct OriginCounter { // 普通的计数器
int count;
std::mutex mutex_;
void add() {
std::lock_guard<std::mutex> lock(mutex_);
++count;
}
void sub() {
std::lock_guard<std::mutex> lock(mutex_);
--count;
}
int get() {
std::lock_guard<std::mutex> lock(mutex_);
return count;
}
};
struct NewCounter { // 使用原子变量的计数器
std::atomic<int> count;
void add() {
++count;
// count.store(++count);这种方式也可以
}
void sub() {
--count;
// count.store(--count);
}
int get() {
return count.load();
}
};
是不是使用原子变量更加方便了呢?
std::call_once 相关
std::once_flag onceflag;
void CallOnce() {
std::call_once(onceflag, []() {
cout << "call once" << endl;
});
}
int main() {
std::thread threads[5];
for (int i = 0; i < 5; ++i) {
threads[i] = std::thread(CallOnce);
}
for (auto& th : threads) {
th.join();
}
return 0;
}
volatile 相关
貌似把
int *p = xxx;
int a = *p;
int b = *p;
注意:
std::condition_variable 相关
条件变量是
这里使用条件变量实现一个
class CountDownLatch {
public:
explicit CountDownLatch(uint32_t count) : count_(count);
void CountDown() {
std::unique_lock<std::mutex> lock(mutex_);
--count_;
if (count_ == 0) {
cv_.notify_all();
}
}
void Await(uint32_t time_ms = 0) {
std::unique_lock<std::mutex> lock(mutex_);
while (count_ > 0) {
if (time_ms > 0) {
cv_.wait_for(lock, std::chrono::milliseconds(time_ms));
} else {
cv_.wait(lock);
}
}
}
uint32_t GetCount() const {
std::unique_lock<std::mutex> lock(mutex_);
return count_;
}
private:
std::condition_variable cv_;
mutable std::mutex mutex_;
uint32_t count_ = 0;
};
关于条件变量其实还涉及到通知丢失和虚假唤醒问题,因为不是本文的主题,这里暂不介绍,大家有需要可以留言。
std::future 相关
std::promise 与std::future 配合使用
#include <functional>
#include <future>
#include <iostream>
#include <thread>
using namespace std;
void func(std::future<int>& fut) {
int x = fut.get();
cout << "value: " << x << endl;
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t(func, std::ref(fut));
prom.set_value(144);
t.join();
return 0;
}
std::packaged_task 与std::future 配合使用
#include <functional>
#include <future>
#include <iostream>
#include <thread>
using namespace std;
int func(int in) {
return in + 1;
}
int main() {
std::packaged_task<int(int)> task(func);
std::future<int> fut = task.get_future();
std::thread(std::move(task), 5).detach();
cout << "result " << fut.get() << endl;
return 0;
}
三者之间的关系
async 相关
#include <functional>
#include <future>
#include <iostream>
#include <thread>
using namespace std;
int func(int in) { return in + 1; }
int main() {
auto res = std::async(func, 5);
// res.wait();
cout << res.get() << endl; // 阻塞直到函数返回
return 0;
}
使用
async(std::launch::async | std::launch::deferred, func, args...);
第一个参数是创建策略:
std::launch::async 表示任务执行在另一线程std::launch::deferred 表示延迟执行任务,调用get 或者wait 时才会执行,不会创建线程,惰性执行在当前线程。
如果不明确指定创建策略,以上两个都不是
若从
std::async(std::launch::async, []{ f(); }); // 临时量的析构函数等待 f()
std::async(std::launch::async, []{ g(); }); // f() 完成前不开始
注意:关于
有时候如果想真正执行异步操作可以对
template <typename F, typename... Args>
inline auto ReallyAsync(F&& f, Args&&... params) {
return std::async(std::launch::async, std::forward<F>(f), std::forward<Args>(params)...);
}
总结
•
C++11 的异步操作-async
C++11 使用std::async 创建异步程序
void f(int n);
std::thread t(f, n + 1);
t.join();
但是线程毕竟是属于比较低层次的东西,有时候使用有些不便,比如希望获取线程函数的返回结果的时候,就不能直接通过 thread.join()
得到结果,这时就必须定义一个变量,在线程函数中去给这个变量赋值,然后
std::async
,通过这个异步接口可以很方便的获取线程函数的执行结果。std::async
会自动创建一个线程去调用线程函数,它返回一个std::future
,这个
其实async
具体用法以及为什么要用std::async
代替线程的创建之前,先看看std::future
、std::promise
和 std::packaged_task
。
std::future
-
deferred:异步操作还没开始
-
ready:异步操作已经完成
-
timeout:异步操作超时
//查询future的状态
std::future_status status;
do {
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred) {
std::cout << "deferred\n";
} else if (status == std::future_status::timeout) {
std::cout << "timeout\n";
} else if (status == std::future_status::ready) {
std::cout << "ready!\n";
} while (status != std::future_status::ready);
获取wait
只是等待异步操作完成,没有返回值,wait_for
是超时等待返回结果。
std::promise
std::promise<int> pr;
std::thread t([](std::promise<int>& p){
p.set_value_at_thread_exit(9);
},std::ref(pr));
std::future<int> f = pr.get_future();
auto r = f.get();
std::packaged_task
packaged_task
保存的是一 个函数。它的基本用法:
std::packaged_task<int()> task([](){ return 7; });
std::thread t1(std::ref(task));
std::future<int> f1 = task.get_future();
auto r1 = f1.get();
std::promise、std::packaged_task 和std::future 的关系
看了std::async
相关的几个对象std::future
、std::promise
和std::packaged_task
,其中 std::promise
和std::packaged_task
的结果最终都是通过其内部的std::future
提供了一个访问异步操作结果的机制,它和线程是一个级别的属于低层次的对象,在它之上高一层的是std::packaged_task
和std::promise
,他们内部都有std::packaged_task
包装的是一个异步操作,而std::promise
包装的是一个值,都是为了方便异步操作的,因为有时需要获取线程中的某个值,这时就用std::promise
,而有时需要获一个异步操作的返回值,这时就用std::packaged_task
。
那 std::promise
和std::packaged_task
之间又是什么关系呢?说他们没关系也没关系,说他们有关系也有关系,都取决于如何使用他们了,可以将一个异步操作的结果保存到std::promise
中。
为什么要用std::async
代替线程的创建
std::async
是为了让开发者的少费点脑子的,它让这三个对象默契的工作。大概的工作过程是这样的:std::async
先将异步操作用std::packaged_task
包 装起来,然后将异步操作的结果放到std::promise
中,这个过程就是创造未来的过程。外面再通过future.get/wait
来获取这个未来的结果!
现在来看看std::async
的原型
async(std::launch::async | std::launch::deferred, f, args...)
第一个参数是线程的创建策略,有两种策略,默认的策略是立即创建线程:
std::launch::async
:在调用
std::launch::deferred
:延迟加载方式创建线程。调用future
的get
或者wait
时才创建线程。
第二个参数是线程函数,第三个参数是线程函数的参数。
std::async
基本用法
std::future<int> f1 = std::async(std::launch::async, []() {
return 8;
});
cout << f1.get() << endl; //output: 8
std::future<void> f2 = std::async(std::launch::async, []() {
cout << 8 << endl;
//return 8;
});
f2.wait(); //output: 8
std::future<int> future = std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::seconds(3));
return 8;
});
std::cout << "waiting...\n";
//Test12();
std::future_status status;
Sleep(3000);
do {
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred) {
std::cout << "deferred\n";
}
else if (status == std::future_status::timeout) {
std::cout << "timeout\n";
}
else if (status == std::future_status::ready) {
std::cout << "ready!\n";
}
} while (status != std::future_status::ready);
std::cout << "result is " << future.get() << '\n';
可能的结果:waiting… timeout timeout ready! result is 8
总结
std::async
是更高层次上的异步操作,它的存在可以使开发者不用关注线程创建内部细节,就能方便的获取异步执行状态和结果,还可以指定线程创建策略,应该用std::async
替代线程的创建,让它成为做异步操作的首选。
C++11 新特性之智能指针
-
std::shared_ptr
-
std::weak_ptr
-
std::unique_ptr
shared_ptr
使用方法如下:
struct ClassWrapper {
ClassWrapper() {
cout << "construct" << endl;
data = new int[10];
}
~ClassWrapper() {
cout << "deconstruct" << endl;
if (data != nullptr) {
delete[] data;
}
}
void Print() {
cout << "print" << endl;
}
int* data;
};
void Func(std::shared_ptr<ClassWrapper> ptr) {
ptr->Print();
}
int main() {
auto smart_ptr = std::make_shared<ClassWrapper>();
auto ptr2 = smart_ptr; // 引用计数+1
ptr2->Print();
Func(smart_ptr); // 引用计数+1
smart_ptr->Print();
ClassWrapper *p = smart_ptr.get(); // 可以通过get获取裸指针
p->Print();
return 0;
}
智能指针还可以自定义删除器,在引用计数为
std::shared_ptr<int> ptr(new int, [](int *p){ delete p; });
关于
• 不要用一个裸指针初始化多个
• 通过
class A {
shared_ptr<A> GetSelf() {
return shared_from_this();
// return shared_ptr<A>(this); 错误,会导致double free
}
};
-
尽量使用
make_shared ,少用new 。 -
不要
delete get() 返回来的裸指针。 -
不是
new 出来的空间要自定义删除器。 -
要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。
using namespace std;
struct A;
struct B;
struct A {
std::shared_ptr<B> bptr;
~A() {
cout << "A delete" << endl;
}
};
struct B {
std::shared_ptr<A> aptr;
~B() {
cout << "B delete" << endl;
}
};
int main() {
auto aaptr = std::make_shared<A>();
auto bbptr = std::make_shared<B>();
aaptr->bptr = bbptr;
bbptr->aptr = aaptr;
return 0;
}
上面代码,产生了循环引用,导致
weak_ptr
- 作用
1 :返回this 指针,上面介绍的shared_from_this() 其实就是通过weak_ptr 返回的this 指针。 - 作用
2 :解决循环引用问题。
struct A;
struct B;
struct A {
std::shared_ptr<B> bptr;
~A() {
cout << "A delete" << endl;
}
void Print() {
cout << "A" << endl;
}
};
struct B {
std::weak_ptr<A> aptr; // 这里改成weak_ptr
~B() {
cout << "B delete" << endl;
}
void PrintA() {
if (!aptr.expired()) { // 监视shared_ptr的生命周期
auto ptr = aptr.lock();
ptr->Print();
}
}
};
int main() {
auto aaptr = std::make_shared<A>();
auto bbptr = std::make_shared<B>();
aaptr->bptr = bbptr;
bbptr->aptr = aaptr;
bbptr->PrintA();
return 0;
}
输出:
A
A delete
B delete
unique_ptr
using namespace std;
struct A {
~A() {
cout << "A delete" << endl;
}
void Print() {
cout << "A" << endl;
}
};
int main() {
auto ptr = std::unique_ptr<A>(new A);
auto tptr = std::make_unique<A>(); // error, c++11还不行,需要c++14
std::unique_ptr<A> tem = ptr; // error, unique_ptr不允许移动
ptr->Print();
return 0;
}