记录一下《现代C++教程》中的要点。
现代C++是指C++11之后的语法特性,如无特别说明,下面的语法特性均是C++11后才可使用。
作用:
NULL
赋空指针;使用:
char *a = nullptr;
NULL = (void*)0
或者NULL = 0
;void *
隐式转换到其他类型的指针,必须显式转换;NULL
就不知道是调用int
还是xxx *
类型的重载函数;nullptr
用于区分空指针和0,而弃用NULL
;nullptr
所占空间和void*
相同;NULL
和\0
的值都是0,所以它们取反是可以转为true
布尔值的;nullptr
的值不是0,所以它取反并不能转为true
,应当用ptr == nullptr
来判断指针是否为空;作用:
使用:
constexpr
声明的对象:
constexpr
组成的表达式)初始化;constexpr
声明的函数:
using
指令、typedef
语句、static_assert
断言和return
语句外,不能出现其他语句;// 对象构造函数
constexpr int a = 1 + 2 + 3;
// 函数
constexpr int fibonacci(const int n) {
return n==1 || n==2 ? 1 : fibonacci(n-1) + fibonacci(n-2);
}
int arr_1[a];
int arr_2[fibonacci(5)];
if(
const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 3);
itr != vec.end()
)
{
*itr = 4;
}
作用:
class object = {}
的形式进行初始化;使用:
std::initializer_list<参数类型> list
;class MagicFoo {
public:
std::vector<int> vec;
// 构造函数中使用
MagicFoo(std::initializer_list<int> list) {
for (std::initializer_list<int>::iterator it = list.begin();
it != list.end(); ++it)
vec.push_back(*it);
}
// 一般的成员函数中使用
void foo(std::initializer_list<int> list) {
for (std::initializer_list<int>::iterator it = list.begin();
it != list.end(); ++it)
vec.push_back(*it);
}
};
MagicFoo magicFoo = {1, 2, 3, 4, 5};
magicFoo.foo({6,7,8,9});
作用:
使用:
std::tuple<int, double, std::string> f() {
return std::make_tuple(1, 2.3, "456");
}
// 自动将int、double和string的值绑定到x、y和z上
auto [x, y, z] = f();
作用:
使用:
decltype
搭配推导返回值类型,C++14后可以无需和decltype
搭配使用;auto i = 5; // i被推导为int
auto arr = new auto(10); // arr被推导为int *
auto it = vec.begin(); // it被推导为容器对应的迭代器类型
template<typename T, typename U>
auto add2(T x, U y) -> decltype(x+y){
return x + y;
}
// C++14之后
template<typename T, typename U>
auto add2(T x, U y){
return x + y;
}
作用:
使用:
decltype(表达式)
;decltype(auto)
作为函数的返回类型,它能自动推导转发函数或者封装函数的返回类型,也就是以调用其他函数作为返回类型的情况;auto x = 1;
auto y = 2;
// 用x+y表达式的类型定义z
decltype(x+y) z;
// 比较x和int类型是否相同
if (std::is_same<decltype(x), int>::value)
std::cout << "type x == int" << std::endl;
// 用decltype(auto)自动推导封装的返回类型
std::string look_up_a_string_1() {
return lookup1();
}
decltype(auto) look_up_a_string_1() {
return lookup1();
}
作用:
使用:
// 比较x和int类型是否相同
if (std::is_same<decltype(x), int>::value)
std::cout << "type x == int" << std::endl;
作用:
使用:
// 注意泛型的实例化在编译过程中就已经实现了
template<typename T>
auto print_type_info(const T& t) {
if constexpr (std::is_integral<T>::value) {
return t + 1;
} else {
return t + 0.001;
}
}
int main() {
std::cout << print_type_info(5) << std::endl;
std::cout << print_type_info(3.14) << std::endl;
}
/* 编译时的代码为:
int print_type_info(const int& t) {
return t + 1;
}
double print_type_info(const double& t) {
return t + 0.001;
}
int main() {
std::cout << print_type_info(5) << std::endl;
std::cout << print_type_info(3.14) << std::endl;
}
*/
作用:
使用:
for(auto element: 实现了迭代器的对象)
,然后在循环中可以用element
读取对象的每个元素;for(auto &element: 实现了迭代器的对象)
,然后在循环中可以用element
读写对象的每个元素;for (auto element : vec) {
std::cout << element << std::endl; // read only
}
for (auto &element : vec) {
element += 1; // read and write
}
作用:
extern
就代表在别处实例化,在本文件中使用但不重复实例化;使用:
template class 模板类名<实例化类型>
显式实例化;template 函数返回值类型 模板函数名<实例化类型>(参数类型)
显式实例化;extern template class 模板类名<实例化类型>
显式实例化;extern template 函数返回值类型 模板函数名<实例化类型>(参数类型)
显式实例化;// 在本编译文件中实例化模板
template class std::vector<bool>;
template int add<int>(int t1, int t2);
// 不在该当前编译文件中实例化模板
extern template class std::vector<double>;
extern template int add<int>(int t1, int t2);
作用:
使用:
std::vector<std::vector<int>> matrix;
>>
会被认为是右移运算符;作用:
typedef
;使用:
using namespace 命名空间名称
引入命名空间(传统C++);using 基类::基类成员
在子类中改变引用的基类成员的权限;using 别名 = 类型或者模板
指定别名;// 命名空间
using namespace std;
using namespace std::vector;
// 在子类中改变基类成员的权限
class Base{
protected:
int member;
};
class Derived: private Base { // 虽然是私有继承
public:
using Base::member; // 但用using后member成为了子类的public成员
}
// 指定普通类型别名
using ULL = unsigned long long; //typedef unsigned long long ULL;
// 指定函数类型别名
using func = void(*)(int, int); //typedef void(*func)(int, int);
// 指定类成员函数类型别名
using handler_t = void(ProcessPool::*)(int);
// 指定模板别名
template <typename T>
using mapInt = std::map<int, T>;
mapInt<bool> bmap;
typedef
不能为模板定义别名,因为模板并不是类型,而是用来产生类型的;typedef
在定义函数指针别名时的写法很独特,在形式上并不规整;using
可以完全取代typedef
;作用:
使用:
sizeof()
函数搭配使用,实现模板参数的拆包;template <typename... TS>
void magic(Ts... args) {
// 输出参数的个数
std::cout << sizeof...(args) << std::endl;
}
// 1. 用递归实现模板参数的拆包,终止函数是一个参数的函数
// 这样传入的可变参数最少是1个
template<typename T0>
void printf1(T0 value) {
// 仅一个参数
std::cout << value << std::endl;
}
template<typename T, typename... Ts>
void printf1(T value, Ts... args) {
// 函数重载,多个参数
std::cout << value << std::endl;
printf1(args...);
}
int main() {
printf1(1, 2, "123", 1.1);
return 0;
}
// 2. 用递归实现模板参数的拆包,终止函数是无模板且无参数的函数
// 这样传入的可变参数可以是0个
void printf1() {
// 无参数
return;
}
template<typename T, typename... Ts>
void printf1(T value, Ts... args) {
// 函数重载,多个参数
std::cout << value << std::endl;
printf1(args...);
}
int main() {
printf1(1, 2, "123", 1.1);
return 0;
}
// 3. 用逗号表达式实现模板参数的拆包
// 但这种方式只适用于各个参数的处理方式相同的情况,使用的范围比较局限
template<typename T>
void printf1(T value) {
std::cout << value << std::endl;
}
template<typename... Ts>
void printf1(Ts... args) {
// 使用std::initializer_list
int arr[] = { (printf1(args), 0)... };
// 等价于用下面的方式
// std::initializer_list {(printf1(args), 0)...};
}
int main() {
printf1(1, 2, "123", 1.1);
return 0;
}
// C++17后可以这样实现拆包
template<typename T0, typename... T>
void printf2(T0 t0, T... t) {
// 一个或者多个参数
std::cout << t0 << std::endl;
if constexpr (sizeof...(t) > 0) printf2(t...);
}
作用:
使用:
template <typename T, int BufSize>
class buffer_t {
public:
T& alloc();
void free(T& item);
private:
T data[BufSize];
}
buffer_t<int, 100> buf; // 100 作为模板参数
(1) std::is_same
:
(2) std::decay
:
const
和左右值引用,(3) std::enable_if
:
// 比较类型1是否和类型2相同,如果是,value=true,反之,value=false
std::is_same<类型1, 类型2>::value
// 获得类型的退化类型(基本类型)
std::decay<类型>::type
// 如果布尔值=true,则type=类型,否则编译出错(类型默认为void)
std::enable_if<布尔值, 类型>::type
作用:
使用:
class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() { // 委托Base() 构造函数
value2 = value;
}
};
作用:
使用:
using
关键字实现;class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() { // 委托Base() 构造函数
value2 = value;
}
};
class Subclass : public Base {
public:
using Base::Base; // 继承构造
};
作用:
使用:
override
保证当前重载的是基类的虚函数;final
保证子类不会再覆盖当前类重载的虚函数,或者保证不会再派生子类;struct Base {
virtual void foo(int);
};
struct SubClass: Base {
virtual void foo(int) override; // 合法
//virtual void foo(float) override; // 非法, 父类没有此虚函数
};
struct Base {
virtual void foo() final;
};
struct SubClass1 final: Base {
}; // 合法
struct SubClass2 : SubClass1 {
}; // 非法, SubClass1 已final
struct SubClass3: Base {
void foo(); // 非法, foo 已final
};
作用:
使用:
函数定义 = default
则显式使用默认函数;函数定义 = delete
则显式禁用默认函数;class Magic {
public:
Magic() = default; // 显式声明使用编译器生成的构造
Magic& operator=(const Magic&) = delete; // 显式声明拒绝编译器生成构造
Magic(int magic_number);
}
作用:
int
类型;使用:
enum class 枚举类名: 类型 {};
定义枚举类,就可以指定枚举类的类型;// 传统C++枚举类型
enum color_set {red, blue, green};
color_set color1;
color1 = red;
color_set color2 = color1;
int i = color1; // 相当于int i = 0;
//color1 = 1; // 不允许将int赋值给enum
cout << color1; // 相当于cout << int(0);
//cin >> color1; // 不允许输入
// 强类型枚举
enum class color_set1: unsigned int {red, blue, green};
enum class color_set2: int {red, blue, green};
color_set1 color1 = red;
color_set2 color2 = red;
//color1 == color2 // 非法
//int i = color1; // 非法
//color1 == 0 // 非法
int
类型;int
不同,枚举变量的取值是有限制的,由它定义时的标识符数量决定;作用:
工作原理:
operator()
运算符;使用:
[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 { // 函数体 }
,包括:
[外部变量名]
,拷贝一个外部变量的副本传入函数;[&外部变量名]
,将外部变量的引用传入函数;[&]
或者[=]
,让编译器自动推导所需的引用捕获或者值捕获;// 和STL算法库中的函数搭配使用
sort(testdata.begin(), testdata.end(), [](int a, int b){
return a > b; });
for_each(a, a+4, [=](int x) {
cout << x << " "; });
auto pos = find_if(coll.cbegin(), coll.cend(), [=](int i) {
return i > x && i < y; });
vec_data.erase(std::remove_if(vec.date.begin(), vec_data.end(), [](int i) {
return n < x;}), vec_data.end());
// C++14后,可以在参数列表中使用auto
auto add = [](auto x, auto y) {
return x + y;
};
cout << add(1, 4) << endl;
作用:
使用:
#include
;std::function<函数返回类型(函数参数类型)> 容器名 = 函数名或者lambda表达式
;#include
int foo(int para) {
return para;
}
// 封装函数foo
std::function<int(int)> func = foo;
int important = 10;
// 封装lambda表达式
std::function<int(int)> func2 = [&](int value) -> int {
return 1+value+important;
};
std::cout << func(10) << std::endl;
std::cout << func2(10) << std::endl;
作用:
使用:
#include
;#include
// 1. 绑定普通函数
void print(int a, int b, int c)
{
std::cout << "a = " << a << ", b=" << b << ", c=" << c << "\n\n";
}
...
// 将占位符2绑定到a,将2绑定到b,将占位符1绑定到c,生成新函数func(_1, _2)
auto func1 = std::bind(print, std::placeholders::_2, 2, std::placeholders::_1);
func1(3, 4); // 相当于是print(4, 2, 3)
...
// 2. 绑定类成员函数
class A {
public:
void print(int x, int y) {
std::cout << "x + y = " << x + y << std::endl;
}
};
...
A a;
// 将a对象的成员函数绑定到f2上
auto func2 = std::bind(&A::print, &a, 1, std::placeholders::_1);
func2(2); // 相当于是a.print(1,2)
...
T &
;std::string & str
T &&
;
std::move(左值变量)
将左值转换成右值,注意,这样会让原来的左值变量不可被访问;std::string && str
std::string lv1 = "string,"; // lv1 是一个左值
// std::string&& r1 = lv1; // 非法, 右值引用不能引用左值
std::string&& rv1 = std::move(lv1); // 合法, std::move 可以将左值转移为右值
std::cout << rv1 << std::endl; // string,
const std::string& lv2 = lv1 + lv1; // 合法, 常量左值引用能够延长临时变量的生命周期
// lv2 += "Test"; // 非法, 常量引用无法被修改
std::cout << lv2 << std::endl; // string,string,
std::string&& rv2 = lv1 + lv2; // 合法, 右值引用延长临时对象生命周期
rv2 += "Test"; // 合法, 非常量引用能够修改临时变量
std::cout << rv2 << std::endl; // string,string,string,Test
拷贝语义:
移动语义:
new
申请的空间:
nullptr
完成资源的移动;new
的空间,包括指针和POD类型等;
涉及的相关函数:
std::move()
:
移动构造函数A(A&& _a)
:
移动赋值运算符重载函数A& operator=(A&& _a)
:
实现移动语义:
std::move()
调用移动构造函数或者移动赋值运算符重载函数,实现移动语义;const A &
常量左值引用也可以接右值引用;const A &
常量左值引用也可以接右值引用;一个代码的例子如下:
class A{
public:
char* ptr;
int val;
std::string s;
A() :ptr("hello"), val(5), s("world") {};
A(const A& _a) {
// 拷贝语义
printf("copy constructor\n");
int _cnt = 0;
while (*(_a.ptr + _cnt) != '\0') {
_cnt++;
}
ptr = new char[_cnt + 1];
memcpy(ptr, _a.ptr, sizeof(char) * (_cnt + 1));
val = _a.val;
s = _a.s;
}
A(A&& _a) {
// 移动语义
printf("move constructor\n");
val = std::move(_a.val);
ptr = std::move(_a.ptr);
s = std::move(_a.s);
_a.val = 0;
_a.ptr = nullptr;
}
A& operator=(const A& _a) {
// 拷贝语义
printf("copy operator =\n");
int _cnt = 0;
while (*(_a.ptr + _cnt) != '\0') {
_cnt++;
}
ptr = new char[_cnt + 1];
memcpy(ptr, _a.ptr, sizeof(char) * (_cnt + 1));
val = _a.val;
s = _a.s;
}
A& operator=(A&& _a) {
// 移动语义
printf("move operator =\n");
val = std::move(_a.val);
ptr = std::move(_a.ptr);
s = std::move(_a.s);
_a.val = 0;
_a.ptr = nullptr;
}
};
A a;
printf("c: %s %s\n", a.ptr, a.s.c_str());
printf("a: %d\n", &a.ptr);
// 拷贝语义
A b(a);
printf("c: %s %s\n", b.ptr, b.s.c_str());
printf("b: %d\n", &b.ptr);
// 移动语义
A c(std::move(a));
printf("c: %s %s\n", c.ptr, c.s.c_str());
printf("c: %d\n", &c.ptr);
auto func = []() {
A tmp;
tmp.ptr = "hello again";
tmp.val = 10;
tmp.s = "new world";
return tmp;
};
A d;
// 移动语义
d = func();
printf("d: %s\n", d.ptr);
printf("d: %s\n", d.s.c_str());
A e;
// 移动语义
e = std::move(d);
printf("e: %s\n", e.ptr);
printf("e: %s\n", e.s.c_str());
T&&
;auto
变量类型推导;decltype
表达式类型推导;typedef
或者using
别名推导;// 模板函数
template<typename T>
void f(T&& param);
// auto自动推导
auto&& var2 = var1;
// typedef自动推导
template<typename T>
typedef T&& RRef;
// using自动推导
template<typename T>
using RRef = T&&;
// decltype自动推导
decltype(w1)&& v2 = w2;
作用:
使用:
std::forward<右值或左值引用类型>(右值或左值引用变量)
可以进行完美转发;void pass(T&& v) {
// 总作为左值转发
std::cout << " 普通传参: ";
reference(v);
// 强制转为右值转发
std::cout << " std::move 传参: ";
reference(std::move(v));
// 使用std::forward转发
std::cout << " std::forward 传参: ";
reference(std::forward<T>(v));
// static_cast转换也符合引用坍缩规则
std::cout << "static_cast 传参: " ;
reference(static_cast<T&&>(v));
}
std::forward()
的底层实现如下:
static_cast
和引用坍缩规则;arg
的类型,
arg
类型就变为左值,不是完美转发了;T&&
类型;
T
决定;T&&
还是右值,反之为左值;static_cast
是完美转发;template <typename T>
T&& forward(typename std::remove_reference<T>::type& arg) noexcept {
return static_cast<T&&>(arg);
}
作用:
使用:
#include
;std::make_tuple(参数1,参数2...)
用于返回由各参数组成的std::tuple类型元组;std::get<元组下标>(元组变量)
用于获得元组变量对应下标的元素,可读写;std::tie(变量名1, 变量名2...) = 元组变量
用于将元组变量拆包,然后赋值给对应的变量名,可以用std::ignore
作变量名占位符;std::tuple_cat(元组变量1, 元组变量2)
用于合并两个元组;std::tuple_len(元组变量)
用于返回元组元素个数(元组长度);std::get<元素类型>(元组变量)
来获得元组中的该类型元素,但如果该类型的元素不唯一,则会有编译期错误;std::tuple_index(元组变量, 元组下标)
获得元组对应下标的元素;auto student = std::make_tuple(3.8, ’A’, " 张三");
std::get<0>(student) = 3.6; // 修改元组的元素
cout << std::get<0>(student) << endl; // 读取元组的元素
std::get<double>(student) = 3.6 // C++14后
std::tie(gpa, std::ignore, name) = student;
auto new_tuple = std::tuple_cat(get_student(1), std::move(t));
for(int i = 0; i != tuple_len(new_tuple); ++i)
// 运行期索引,C++17后
std::cout << tuple_index(new_tuple, i) << std::endl;
作用:
使用:
#include
;std::make_shared<对象类型>(对象值)
可以生成一个对象,并返回它的shared_ptr指针,推荐这样使用;get()
可以获取原始指针而不增加引用计数;reset()
可以将当前shared_ptr的指向和引用计数清空,同时将其他指向同一个对象的shared_ptr的引用计数减一;nullptr
;std::make_shared<int> pointer0(new int); // 不推荐这样使用
auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = "
<< pointer2.use_count() << std::endl; // pointer2 已reset; 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // pointer3 已reset; 0
作用:
使用:
#include
;std::unique_ptr<对象类型> 智能指针名(指向对象的指针)
生成一个对象;std::make_unique<对象类型>(对象值)
生成一个对象,并返回它的unique_ptr指针,推荐这样使用;std::move(unique_ptr)
把它指向的对象转移给别的std::unique_ptr;nullptr
;std::unique_ptr<Foo> p1(std::make_unique<Foo>());
// p1 不空, 输出
if (p1) p1->foo();
{
std::unique_ptr<Foo> p2(std::move(p1));
// p2 不空, 输出
f(*p2);
// p2 不空, 输出
if(p2) p2->foo();
// p1 为空, 无输出
if(p1) p1->foo();
p1 = std::move(p2);
// p2 为空, 无输出
if(p2) p2->foo();
std::cout << "p2 被销毁" << std::endl;
}
// p1 不空, 输出
if (p1) p1->foo();
// Foo 的实例会在离开作用域时被销毁
作用:
使用:
#include
;expired()
可以检查当前指向的对象是否还存在,返回布尔类型;use_count()
可以检查当前指向的对象的引用计数;lock()
可以返回当前指向对象的一个shared_ptr指针;shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
auto new_sp = wp.lock();
if(wp.expired()) {
cout << "weak_ptr无效,资源已释放";
}
else {
cout << "weak_ptr有效, *new_sp = " << *new_sp << endl;
}
weak_ptr
:
shared_ptr
或者weak_ptr
初始化;shared_ptr
或者weak_ptr
赋值;unique_ptr
:
std::move(unique_ptr)
初始化;nullptr
、std::move(unique_ptr)
或者unique_ptr<对象类型>(对象指针)
赋值;shared_ptr
:
shared_ptr
、std::move(unique_ptr)
或者weak_ptr
初始化;nullptr
、shared_ptr
、std::move(unique_ptr)
或者shared_ptr<对象类型>(对象指针)
赋值;作用:
使用:
#include
;std::thread 线程实例名(线程执行的函数名, 函数参数1, 函数参数2...)
创建一个执行函数的线程;
std::bind()
一样,如果是值传递或者指针传递,则和普通函数一样,但如果是引用传递,则需要用std::ref(对象名)
代替直接使用对象名
作为实参;
std::ref(对象名)
,则普通的左值引用传递仍然是值传递的实现,右值引用传递(使用std::move()
)才是真正引用传递的实现;join()
用于阻塞创建线程实例的线程直至线程实例执行完毕;
terminate called without an active exception
错误;join()
使得主线程等待各个线程结束后再结束;detach()
用于将线程实例从创建线程实例的线程中脱离开,成为守护线程,这样:
std::future
在创建线程实例的线程中获得线程实例执行的结果;#include
#include
int main() {
std::thread t([](){
std::cout << "hello world." << std::endl;
});
// 阻塞main()直至t线程执行完毕
t.join();
return 0;
}
作用:
使用:
#include
;lock()
:当前线程尝试锁住该互斥量,
unlock()
;unlock()
:当前线程释放该互斥量;try_lock()
:尝试锁住互斥量,
unlock()
;std::lock_guard<互斥量类型> 名称(互斥量变量)
:mutex的RAII语法,用于:
mutex对象.lock()
,在对象构造时即尝试为mutex对象上锁;unlock()
释放;release()
之前一定要先unlock()
,否则互斥量是无法释放锁的,会导致其他地方陷入竞争锁死锁;release()
,尤其是不要误用了release()
;std::unique_lock<互斥量类型> 名称(互斥量变量)
:mutex的RAII语法,比lock_guard更加灵活,允许:
#include // std::cout
#include // std::thread
#include // std::mutex
// 调用mutex成员函数
volatile int counter(0); // non-atomic counter
std::mutex mtx; // locks access to counter
void attempt_10k_increases() {
for (int i=0; i<10000; ++i) {
if (mtx.try_lock()) { // only increase if currently not locked:
++counter;
mtx.unlock();
}
}
}
// 使用lock_guard
void critical_section(int change_v) {
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx); // 相当于mtx.lock()
// 执行竞争操作
v = change_v;
// 离开此作用域后mtx 会被释放
}
// 使用unique_lock
void critical_section(int change_v) {
static std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx); // 相当于mtx.lock()
// 执行竞争操作
v = change_v;
std::cout << v << std::endl;
// 将锁进行释放
lock.unlock(); // 相当于mtx.unlock()
// 在此期间,任何人都可以抢夺v 的持有权
// 开始另一组竞争操作,再次加锁
lock.lock(); // 相当于mtx.lock()
v += 1;
std::cout << v << std::endl;
}
作用:
future.get()
或者thread.join()
;thread
执行函数功能;使用:
#include
;std::packaged_task<函数返回类型(函数参数类型)> 容器名(函数名或者lambda表达式)
创建一个std::packaged_task对象;std::futrue<函数返回类型> 容器名 = std::packaged_task对象.get_future()
创建一个获得std::packaged_task执行结果的future对象;get()
获得std::packaged_task执行结果;#include // std::cout
#include // std::packaged_task, std::future
#include // std::chrono::seconds
#include // std::thread, std::this_thread::sleep_for
// count down taking a second for each value:
int countdown (int from, int to) {
for (int i=from; i!=to; --i) {
std::cout << i << '\n';
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Finished!\n";
return from - to;
}
int main ()
{
std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
std::future<int> ret = task.get_future(); // 获得与 packaged_task 共享状态相关联的 future 对象.
std::thread th(std::move(task), 10, 0); //创建一个新线程完成计数任务.
int value = ret.get(); // 等待任务完成并获取结果.
std::cout << "The countdown lasted for " << value << " seconds.\n";
th.join();
return 0;
}
作用:
使用:
#include
;std::condition_variable 条件变量名;
定义条件变量;条件变量对象.wait(unique_lock对象)
,当前线程被阻塞,同时释放拥有的互斥量对象锁;条件变量对象.wait(unique_lock对象, bool类型返回值函数)
,仅当函数返回值为false
,才将当前线程阻塞,同时释放拥有的互斥量对象锁;条件变量对象.notify_all()
,释放拥有的互斥量对象锁,唤醒所有wait()
的线程,并让它们竞争互斥信号量;条件变量对象.notify_one()
,释放拥有的互斥量对象锁,唤醒某一个wait()
的线程,并让它们竞争互斥信号量,但这样没有办法实现并发的竞争,效率较低,不太推荐在并发环境中使用;wait()
的对象是持有mutex
的unique_lock
对象,而非mutex
对象本身;#include
#include
#include
#include
#include
#include
int main() {
std::queue<int> produced_nums;
std::mutex mtx;
std::condition_variable cv;
bool notified = false; // 通知信号
// 生产者
auto producer = [&]() {
for (int i = 0; ; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(900));
std::unique_lock<std::mutex> lock(mtx);
std::cout << "producing " << i << std::endl;
produced_nums.push(i);
notified = true;
// 释放mtx,唤醒所有wait(mtx)的线程
cv.notify_all();
}
};
// 消费者
auto consumer = [&]() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
while (!notified) { // 避免虚假唤醒
// 释放mtx,等待别的线程唤醒自己
cv.wait(lock);
// 虚假唤醒:可能是由于别的原因而非notify()让自己获得了互斥量锁
}
// 消费者慢于生产者,则短暂取消锁,使得生产者有机会在消费者消费前继续生产
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// 加锁消费
lock.lock();
while (!produced_nums.empty()) {
std::cout << "consuming " << produced_nums.front() << std::endl;
produced_nums.pop();
}
notified = false;
}
};
// 分别在不同的线程中运行
std::thread p(producer);
std::thread cs[2];
for (int i = 0; i < 2; ++i) {
cs[i] = std::thread(consumer);
}
p.join();
for (int i = 0; i < 2; ++i) {
cs[i].join();
}
return 0;
}
作用:
使用:
#include
;std::atomic::is_lock_free()
函数来检查T类型的对应原子类型是否支持真正的原子操作;fetch_add()
:加法操作,也有“+”运算符重载;fetch_sub()
:减法操作,也有“-”运算符重载;load()
:返回原子变量值;store()
:更新原子变量值;exchange()
:将原子变量设置为新值,并返回旧值;atomic
变量重载函数和成员函数的原子性:
x = x + y
这个运算不是原子的;#include
#include
#include
//std::atomic count = {0};
int main() {
std::atomic<int> count;
std::atomic_init(&count, 0);
std::thread t1([](){
count.fetch_add(1);
});
std::thread t2([](){
count++; // 等价于fetch_add
count += 1; // 等价于fetch_add
});
t1.join();
t2.join();
std::cout << count << std::endl;
return 0;
}
x = 1
,写x = 2
,读x = 2
;x = 3
,但不保证看到的x
写入1一定在写入2之前执行;c
在a
和b
的写入之后执行;x = 4
,但当前不一定读到;load()
、store()
和fetch_add()
增加参数std::memory_order_xxx
控制原子操作的一致性,进而降低同步的开销;std::memory_order_seq_cst
;counter.fetch_add(1, std::memory_order_seq_cst);
(2) 释放/获取模型:
std::memory_order_release
;
store()
;std::memory_order_acquire
;
load()
;std::memory_order_acq_rel
;
一些例子如下:
a = 0;
b = 0;
c = 0;
thread 1:
{
a = 1;
b.store(2, memory_order_relaxed);
c.store(3, memory_order_release);
}
thread 2:
{
while (c.load(memory_order_acquire) != 3)
;
// 以下 assert 永远不会失败
assert(a == 1 && b == 2);
assert(b.load(memory_order_relaxed) == 2);
}
thread 1:
{
std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}
thread 2:
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
// 以下 assert 永远不会失败
assert(*p2 == "Hello");
assert(data == 42);
}
std::memory_order_consume
;
load()
;a = 0;
c = 0;
thread 1:
{
a = 1;
c.store(3, memory_order_release);
}
thread 2:
{
while (c.load(memory_order_consume) != 3)
;
assert(a == 1); // assert 可能失败也可能不失败
}
(4) 宽松模型:
std::memory_order_relaxed
;一些总结如下:
std::atomic
中的CAS函数有两个:
原子变量.compare_exchange_weak(期望值, 设置值)
:
原子变量.compare_exchange_strong(期望值, 设置值)
:
#include
#include
#include
int main() {
std::atomic<int> count(0);
std::thread t1([&]() {
for (int i = 0; i < 1000000; ++i) {
/*以下是计数器自旋锁实现*/
int expected = count.load();
// 尝试将count设置为expected+1
// 如果count == expected则设置成功,返回true
// 否则,将expected设置为为count.load(),返回false
while (!count.compare_exchange_weak(expected, expected + 1)) {}
}
});
std::thread t2([&]() {
for (int i = 0; i < 1000000; ++i) {
/*以下是计数器自旋锁实现*/
int expected = count.load();
while (!count.compare_exchange_weak(expected, expected + 1)) {}
}
});
t1.join();
t2.join();
std::cout << "count = " << count.load() << '\n';
return 0;
}
作用:
使用:
函数返回值类型 函数名(函数参数类型) noexcept;
用于声明一个函数不会抛出异常;noexcept(表达式)
用于判断表达式是否有异常;// 可能抛出异常的函数
void may_throw() {
throw true;
}
auto non_block_throw = []{
may_throw();
};
// 不抛出异常的函数
void no_throw() noexcept {
return;
}
auto block_throw = []() noexcept {
no_throw();
};
作用:
使用:
R"(字符串)"
将字符串定义为字符串字面量;""
后缀运算符可自定义整型字面量、浮点型字面量、字符串字面量和字符字面量为字符串字面量;// 字符串字面量自定义必须设置如下的参数列表
std::string operator"" _wow1(const char *wow1, size_t len) {
return std::string(wow1)+"woooooooooow, amazing";
}
std::string operator"" _wow2 (unsigned long long i) {
return std::to_string(i)+"woooooooooow, amazing";
}
int main() {
auto str = "abc"_wow1;
auto num = 1_wow2;
std::cout << str << std::endl;
std::cout << num << std::endl;
return 0;
}
作用:
使用:
alignof(结构体)
:返回结构体的有效对齐值;struct alignas(有效对齐值) 结构体名{};
:修改结构体的有效对齐值,只能往大对齐;struct alignas(4) stTestAlign // 修改有效对齐值为4字节
{
char a;
char b;
stTestAlign()
{
cout << "sizeof(stTestAlign) =" << sizeof(stTestAlign) << endl; //4
cout << "alignof(stTestAlign) =" << alignof(stTestAlign) << endl; //4
}
};
struct stTestAlign
{
char a;
alignas(4) char b; // char原本是1字节,强制作为4字节对齐
stTestAlign()
{
cout << "sizeof(stTestAlign) =" << sizeof(stTestAlign) << endl; //8
cout << "alignof(stTestAlign) =" << alignof(stTestAlign) << endl; //4
}
};
#pragma pack(n)
,由系统的位数和编译器决定,通常默认为8
;定义:
作用:
使用:
co_await Awaitable结构体
:调用一个Awaitable对象,由它内部定义决定它是挂起还是继续以及挂起和恢复时的行为:
await_ready()
:询问Awaitable结构体是否已经准备好而不需要等待;await_suspend()
:传入一个coroutine_handle
类型的参数挂起Awaitable结构体;await_resume()
:协程重新运行时调用该函数,同时返回值,返回值的Promise;co_yield
:暂停执行并返回一个值;co_return
:完成执行并返回一个值;// 用回调函数实现init + 100
using call_back = std::function<void(int)>;
void Add100ByCallback(int init, call_back f) //init是传入的初始值,add之后的结果由回调函数f通知
{
std::thread t([init, f]() {
std::this_thread::sleep_for(std::chrono::seconds(5)); // sleep一下,假装很耗时
f(init + 100); // 耗时的计算完成了,调用回调函数
});
t.detach();
}
// 将回调函数封装成协程结构
struct Add100AWaitable
{
Add100AWaitable(int init):init_(init) {}
bool await_ready() const { return false; }
int await_resume() { return result_; }
void await_suspend(std::experimental::coroutine_handle<> handle)
{
// 定义一个回调函数,在此函数中恢复协程
auto f = [handle, this](int value) mutable {
result_ = value;
handle.resume(); // 这句是关键
};
Add100ByCallback(init_, f);
}
int init_; // 将参数存在这里
int result_; // 将返回值存在这里
};
// 调用协程计算init + 100,可以多次调用
Task Add100ByCoroutine(int init, call_back f)
{
int ret = co_await Add100AWaitable(init);
ret = co_await Add100AWaitable(ret);
ret = co_await Add100AWaitable(ret);
f(ret);
}
作用:
使用:
concepts
关键字;作用:
使用:
export
、module
和import
等关键字;