C++11 新特性简单入门&Cheatsheet

最近在考虑做一下CMU15445提升一下工程项目的水平,同时也学习一下现代C++的coding schema与代码style。project的课程网站上有一个推荐的C++新特性的学习项目BootCamp,简单看了一下写的还是很不错的,从代码加注释的方式解释新特性,而且代码整体也比较贴合现代C++。

这边简单汇总一下这个项目的内容,学习一下,也方便自己后面回顾复习。

项目地址:https://github.com/cmu-db/15445-bootcamp

Reference类

references

就是一个变量的alias

1
2
3
4
5
6
7
void add_three(int &a) { a = a + 3; }

int a = 10;
int &b = a;

add_three(a);

move_semantics

这个部分比较难理解,首先要明白什么是左值和右值。

简单定义:

  • 左值:objects 指向内存中的一个location
  • 右值:任何不是左值的东西

我自己的理解是右值表示: 这个东西本身

移动一个左值到另外一个左值:

1
2
3
4
5
6

std::vector<int> int_array = {1, 2, 3, 4};
std::vector<int> stealing_ints = std::move(int_array);
std::vector<int> &&rvalue_stealing_ints = std::move(stealing_ints);


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32


void move_add_three_and_print(std::vector<int> &&vec) { // 左值引用 &, 右值引用 &&, 取地址 *, **
// && vec 就是rvalue reference
std::vector<int> vec1 = std::move(vec); // 这里移动了vec,所以在函数外面,这个传入的位置的函数已经没有办法访问了,会发生segmentation fault
vec1.push_back(3);
for (const int &item : vec1) {
std::cout << item << " ";
}
std::cout << "\n";
}


void add_three_and_print(std::vector<int> &&vec) { // 没有使用move的情况
vec.push_back(3);
for (const int &item : vec) {
std::cout << item << " ";
}
std::cout << "\n";
}


std::vector<int> int_array2 = {1, 2, 3, 4};
move_add_three_and_print(std::move(int_array2));
// 此时访问会报错,因为里面将这个东西本身的东西移动了
// std::cout << int_array2[1] << std::endl;

std::vector<int> int_array3 = {1, 2, 3, 4};
add_three_and_print(std::move(int_array3));
std::cout << "Printing from int_array3: " << int_array3[1] << std::endl;
// 这种情况访问就没有问题

move constructors

首先引入一个person类,这个类实现了移动构造和移动赋值运算符。并且删除了拷贝构造和拷贝赋值运算符。

所以一旦一个person类被instance化了,这个类不能被拷贝,只能从一个左值移动到另一个左值。

这种删除掉拷贝的东西其实很有用,特别是有时候强制只能存在一个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Person {
public:
Person() : age_(0), nicknames_({}), valid_(true) {}

// 这边构建的时候使用了右值,表示这个构建方式非常高效,没有去深拷贝这个传入的实例
Person(uint32_t age, std::vector<std::string> &&nicknames)
: age_(age), nicknames_(std::move(nicknames)), valid_(true) {}

// 移动构造函数:类名(类名 &&类实例) (传入的是右值)
// nicknames上面用了move,确保了传入的nicknames是移动的而不是拷贝的,move函数会把nickname转成一个右值,也就是代表了nickname这个东西本身
// 在age上没有调用move,因为这只是一个int,太小了不会造成明显的拷贝消耗
Person(Person &&person)
: age_(person.age_), nicknames_(std::move(person.nicknames_)),
valid_(true) {
std::cout << "Calling the move constructor for class Person.\n";
// The moved object's validity tag is set to false.
person.valid_ = false;
}

// 拷贝赋值运算符
Person &operator=(Person &&other) {
std::cout << "Calling the move assignment operator for class Person.\n";
age_ = other.age_;
nicknames_ = std::move(other.nicknames_);
valid_ = true;

other.valid_ = false;
return *this;
}

//这个部分删除了拷贝构造和拷贝赋值运算符
// 所以这个类不能通过拷贝来构建
Person(const Person &) = delete;
Person &operator=(const Person &) = delete;

uint32_t GetAge() { return age_; }

//前面的&符号表示返回的是一个nicknames_[i]的引用,没有返回一个拷贝的值
std::string &GetNicknameAtI(size_t i) { return nicknames_[i]; }

void PrintValid() {
if (valid_) {
std::cout << "Object is valid." << std::endl;
} else {
std::cout << "Object is invalid." << std::endl;
}
}

private:
uint32_t age_;
std::vector<std::string> nicknames_;
// 表示当前类是否被移动了
bool valid_;
};

对于移动构造的调用也比较重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

int main() {
Person andy(15445, {"andy", "pavlo"});
andy.PrintValid(); // valid

// 移动赋值运算符
Person andy1;
andy1 = std::move(andy);

andy1.PrintValid(); // valid
andy.PrintValid(); // invalid

// 移动构造
Person andy2(std::move(andy1));

andy2.PrintValid(); // valid
andy1.PrintValid(); // valid

// (由于这个类里面删除了拷贝构造和拷贝赋值运算符,所以下面的会报错)
// 拷贝赋值
// Person andy3;
// andy3 = andy2;

// 拷贝构造
// Person andy4(andy2);

return 0;
}

模板

templated functions

template的关键字,template<class T>template<typename T>,基本上是一样,具体的区别可以看这个博客

https://mariusbancila.ro/blog/2021/03/15/typename-or-class/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

template <typename T> T add(T a, T b) { return a + b; }

template<typename T, typename U>
void print_two_values(T a, U b) {
std::cout << a << " and " << b << std::endl;
}

// 也可以用来对同一个函数在不同条件下定制
// 当接收float时,输出一种内容,在接收其他条件时输出另外的内容

template <typename T> void print_msg() { std::cout << "Hello world!\n"; }

template <> void print_msg<float>() {
std::cout << "print_msg called with float type!\n";
}


// 模板参数也不用一定是类,可以就接收一种输入,来让函数做不一样的事情。

template <bool T> int add3(int a) {
if (T) {
return a + 3;
}

return a;
}


std::cout << "Printing add<int>(3, 5): " << add<int>(3, 5) << std::endl;
std::cout << "Printing add<float>(2.8, 3.7): " << add<float>(2.8, 3.7) << std::endl;

// 类型是可以自动解析的,但是不建议这样使用
std::cout << "Printing add(3, 5): " << add(3, 5) << std::endl;

print_two_values<int, float>(3, 3.2);

print_msg<int>();
print_msg<float>();

add3<true>(3);
add3<false>(3);

templated classes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

template<typename T>
class Foo {
public:
Foo(T var) : var_(var) {}
void print() {
std::cout << var_ << std::endl;
}
private:
T var_;
};


template<typename T, typename U>
class Foo2 {
public:
Foo2(T var1, U var2)
: var1_(var1)
, var2_(var2) {}
void print() {
std::cout << var1_ << " and " << var2_ << std::endl;
}
private:
T var1_;
U var2_;
};


// 可以用来定制
template<typename T>
class FooSpecial {
public:
FooSpecial(T var) : var_(var) {}
void print() {
std::cout << var_ << std::endl;
}
private:
T var_;
};


template<>
class FooSpecial<float> {
public:
FooSpecial(float var) : var_(var) {}
void print() {
std::cout << "hello float! " << var_ << std::endl;
}
private:
float var_;
};


// 可以使用一个具体的类型的value
template<int T>
class Bar {
public:
Bar() {}
void print_int() {
std::cout << "print int: " << T << std::endl;
}
};



Foo<int> a(3);
Foo b(3.4f);
Foo2<int, float> c(3, 3.2f);
FooSpecial<int> d(5);
FooSpecial<float> e(4.5);
Bar<150> f;

Misc

wrapper class

包装类用来管理资源,memory file sockets或者network connection。

常用 RAII (Resource Acquisition is Initialization) 技术,这种技术保证了资源的lifetime与它的scope密切结合。当一个包装类构建了,它里面的资源就是可用的,当这个类析构了,资源也就同时不可用。

https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii

https://en.cppreference.com/w/cpp/language/raii

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 管理int指针的类 IntPtrManager
// RAII技术的原则是包装类不应该拷贝,所以拷贝运算被删除,但是仍然可以移动所有权。
// 还有一种解释是,wrapper类在类里面释放资源,如果拷贝发生的话可能会对同一个资源多次删除。

class IntPtrManager {
public:
// 每个包装类都是用来初始化一种变量
IntPtrManager() {
ptr_ = new int;
*ptr_ = 0;
}

//接受一个初始变量
IntPtrManager(int val) {
ptr_ = new int;
*ptr_ = val;
}

// 析构
~IntPtrManager() {
// 不要对nullptr调用delete
if (ptr_) {
delete ptr_;
}
}

// 移动构造
// 原来的会变成不可用
IntPtrManager(IntPtrManager&& other) {
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}

// 移动赋值函数
IntPtrManager &operator=(IntPtrManager &&other) {
if (ptr_ == other.ptr_) {
return *this;
}
if (ptr_) {
delete ptr_;
}
ptr_ = other.ptr_;
other.ptr_ = nullptr;
return *this;
}

// 删除拷贝相关
IntPtrManager(const IntPtrManager &) = delete;
IntPtrManager &operator=(const IntPtrManager &) = delete;

// Setter function.
void SetVal(int val) {
*ptr_ = val;
}

// Getter function.
int GetVal() const {
return *ptr_;
}

private:
int *ptr_;

};


IntPtrManager a(445);

std::cout << "1. Value of a is " << a.GetVal() << std::endl;
a.SetVal(645);
std::cout << "2. Value of a is " << a.GetVal() << std::endl;

IntPtrManager b(std::move(a));

// 此时b在管理这个资源,对a调用GetVal()会发生segfault
std::cout << "Value of b is " << b.GetVal() << std::endl;

// 若此时发生析构(例如函数结束),a 不会做什么因为资源的管理到了b,b会对资源进行删除

iterator

迭代器是指向容器内部元素的一种指针。

传统指针也是一种迭代器

1
2
3
4
5
int *array = malloc(sizeof(int) * 10);
int *iter = array;
int zero_elem = *iter;
iter++;
int first_elem = *iter;

两个关键要素:

  • 取值 * : 返回当前指向元素的值
  • 迭代 ++ (后置): 指向位置+1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

// doubly linked list (DLL)

struct Node {
Node(int val)
: next_(nullptr)
, prev_(nullptr)
, value_(val) {}

Node* next_;
Node* prev_;
int value_;
};

class DLLIterator {
public:
DLLIterator(Node* head)
: curr_(head) {}

// 前置++
DLLIterator& operator++() {
curr_ = curr_->next_;
return *this;
}

// 后置++
DLLIterator operator++(int) {
DLLIterator temp = *this;
++*this;
return temp;
}

// 等号重载
bool operator==(const DLLIterator &itr) const {
return itr.curr_ == this->curr_;
}

// 不等号
bool operator!=(const DLLIterator &itr) const {
return itr.curr_ != this->curr_;
}

// 取值
int operator*() {
return curr_->value_;
}

private:
Node* curr_;
};



class DLL {
public:

// 构造函数
DLL()
: head_(nullptr)
, size_(0) {}

// 析构
~DLL() {
Node *current = head_;
while(current != nullptr) {
Node *next = current->next_;
delete current;
current = next;
}
head_ = nullptr;
}

// Function for inserting val at the head of the DLL.
void InsertAtHead(int val) {
Node *new_node = new Node(val);
new_node->next_ = head_;

if (head_ != nullptr) {
head_->prev_ = new_node;
}

head_ = new_node;
size_ += 1;
}

// The Begin() function returns an iterator to the head of the DLL,
// which is the first element to access when iterating through.
DLLIterator Begin() {
return DLLIterator(head_);
}

// The End() function returns an iterator that marks the one-past-the-last
// element of the iterator. In this case, this would be an iterator with
// its current pointer set to nullptr.
DLLIterator End() {
return DLLIterator(nullptr);
}

Node* head_{nullptr};
size_t size_;
};


// 也可以iter++
for (DLLIterator iter = dll.Begin(); iter != dll.End(); ++iter) {
std::cout << *iter << " ";
}


namespaces

给标识符提供一个scope。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
namespace ABC {
void spam(int a) {
std::cout << "Hello from ABC::spam: " << a << std::endl;
}

// 嵌套命名空间
namespace DEF {
void bar(float a) {
std::cout << "Hello from ABC::DEF::bar: " << a << std::endl;
}

void uses_bar(float a) {
std::cout << "Hello from uses_bar: ";
bar(a);
}

// 使用全路径总是可以调用到函数,但是coding效率会下降
void uses_spam(int a) {
std::cout << "Hello from uses_spam: ";
ABC::spam(a);

// 也可以直接调用上一层的spam
// spam(a);
}
}

void uses_DEF_bar(float a) {
std::cout << "Hello from uses_DEF_bar: ";
DEF::bar(a);
}
}

using 的用法:

不推荐将命名空间的所有标识符直接导入当前scope

1
2
using namespace B;
using C::eggs;

STL 容器

STL: (C++ Standard Library)

vectors

这边只涉及一些基础的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
point_vector.push_back(Point(35, 36));
point_vector.emplace_back(37, 38);
// emplace_back 稍微快一点

for (const Point &item : point_vector) { // 加上 const 表示当前accessing是read-only
item.PrintPoint();
}

int_vector.erase(int_vector.begin() + 2);
int_vector.erase(int_vector.begin() + 1, int_vector.end());

// remove_if: #include<algorithm>
// 我的理解是remove_if把所有要remove的元素换到了最后
point_vector.erase(
std::remove_if(point_vector.begin(), point_vector.end(),
[](const Point &point) { return point.GetX() == 37; }),
point_vector.end());

set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

int_set.insert(i);
int_set.emplace(i);

std::set<int>::iterator search = int_set.find(2);
if (int_set.count(11) == 0) { }
if (int_set.count(3) == 1) { }

int_set.erase(4);
int_set.erase(int_set.begin());
int_set.erase(int_set.find(9), int_set.end());

for (std::set<int>::iterator it = int_set.begin(); it != int_set.end(); ++it) {
std::cout << *it << " ";
}

for (const int &elem : int_set) {
std::cout << elem << " ";
}

unordered_map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

std::unordered_map<std::string, int> map;
map.insert({"foo", 2});
map.insert(std::make_pair("jignesh", 445)); // #include <utility>
map.insert({{"spam", 1}, {"eggs", 2}, {"garlic rice", 3}});
map["bacon"] = 5;
map["spam"] = 15;

std::unordered_map<std::string, int>::iterator result = map.find("jignesh");
std::pair<std::string, int> pair = *result;

size_t count = map.count("spam");
map.erase("eggs");
map.erase(map.find("garlic rice"));

for (std::unordered_map<std::string, int>::iterator it = map.begin(); it != map.end(); ++it) {
std::cout << "(" << it->first << ", " << it->second << "), ";
}

for (const std::pair<const std::string, int> &elem : map) {
std::cout << "(" << elem.first << ", " << elem.second << "), ";
}


auto

auto就是自动推断类型的变量,简单的变量类型直接写会比较方便,但是碰到复杂的范型还是用auto增加代码阅读性比较好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

template <typename T, typename U> class Abcdefghijklmnopqrstuvwxyz {
public:
Abcdefghijklmnopqrstuvwxyz(T instance1, U instance2)
: instance1_(instance1), instance2_(instance2) {}

void print() const {
std::cout << "(" << instance1_ << "," << instance2_ << ")\n";
}

private:
T instance1_; // 后面写_表示private变量
U instance2_;
};


template <typename T>
Abcdefghijklmnopqrstuvwxyz<T, T> construct_obj(T instance) {
return Abcdefghijklmnopqrstuvwxyz<T, T>(instance, instance);
}


Abcdefghijklmnopqrstuvwxyz<int, int> obj = construct_obj<int>(2);
auto obj1 = construct_obj<int>(2);


但是,要注意auto只是类型的自动推断,直接用auto就相当于直接写类型,会发生深拷贝。

1
2
3
4

auto copy_int_values = int_values; // 引用发生在类型上
auto& ref_int_values = int_values; // 所以是 auto 和 auto&

常用在循环遍历中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

std::unordered_map<std::string, int> map;
map.insert({{"andy", 445}, {"jignesh", 645}}); // pair 构造函数可以这样写


for (std::unordered_map<std::string, int>::iterator it = map.begin();
it != map.end(); ++it) {
std::cout << "(" << it->first << "," << it->second << ")"
<< " ";
}


for (auto it = map.begin(); it != map.end(); ++it) {
std::cout << "(" << it->first << "," << it->second << ")"
<< " ";
}



for (const auto& elem : vec) { // 更现代的写法
std::cout << elem << " ";
}

STL Memory

主要涉及智能指针,现代c++常用的智能指针主要是 std::unique_ptrstd::shared_ptr

unique_ptr

对一个资源的唯一的指针,没有两个unique_ptr实例可以指向同一个object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Point {
public:
Point() : x_(0), y_(0) {}
Point(int x, int y) : x_(x), y_(y) {}
inline int GetX() { return x_; }
inline int GetY() { return y_; }
inline void SetX(int x) { x_ = x; }
inline void SetY(int y) { y_ = y; }

private:
int x_;
int y_;
};

void SetXTo445(std::unique_ptr<Point> &ptr) { ptr->SetX(445); } // 基本上是用引用的方式传递,因为不会改变owner

std::unique_ptr<Point> u1;
std::unique_ptr<Point> u2 = std::make_unique<Point>();
std::unique_ptr<Point> u3 = std::make_unique<Point>(2, 3);

// 自带bool判断
if (u1) { // u1 empty
std::cout << "u1's value of x is " << u1->GetX() << std::endl;
}

if (u2) { // u2 not empty
std::cout << "u2's value of x is " << u2->GetX() << std::endl;
}

// 因为没有拷贝构造,过不了编译
// std::unique_ptr<Point> u4 = u3;

但是可以移动
std::unique_ptr<Point> u4 = std::move(u3);


shared_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

// 引用
void modify_ptr_via_ref(std::shared_ptr<Point> &point) { point->SetX(15); }

// 右值引用
void modify_ptr_via_rvalue_ref(std::shared_ptr<Point> &&point) {
point->SetY(645);
}

void copy_shared_ptr_in_function(std::shared_ptr<Point> point) {
std::cout << "Use count of shared pointer is " << point.use_count()
<< std::endl;
}


std::shared_ptr<Point> s1;
std::shared_ptr<Point> s2 = std::make_shared<Point>(); // 构造函数调用
std::shared_ptr<Point> s3 = std::make_shared<Point>(2, 3);


std::cout << "Pointer s1 is " << (s1 ? "not empty" : "empty") << std::endl;
std::cout << "Pointer s2 is " << (s2 ? "not empty" : "empty") << std::endl;
std::cout << "Pointer s3 is " << (s3 ? "not empty" : "empty") << std::endl;


std::cout << "Number of shared pointer object instances using the data in s3: " << s3.use_count() << std::endl;

std::shared_ptr<Point> s4 = s3;

std::cout << "Number of shared pointer object instances using the data in s3 " "after one copy: " << s3.use_count() << std::endl;

std::shared_ptr<Point> s5(s4);

std::cout << "Number of shared pointer object instances using the data in s3 " "after two copies: " << s3.use_count() << std::endl;


// 改变ownership
std::shared_ptr<Point> s6 = std::move(s5);

// 此时 s5 是空的
std::cout << "Pointer s5 is " << (s5 ? "not empty" : "empty") << std::endl;
std::cout << "Number of shared pointer object instances using the data in s3 " "after two copies and a move: " << s3.use_count() << std::endl;


modify_ptr_via_ref(s2);
modify_ptr_via_rvalue_ref(std::move(s2));

// 可以用copy的方式传递,在函数内会使count+1
copy_shared_ptr_in_function(s2);

STL 同步

synchronization primitive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <mutex>
#include <thread>

int count = 0;
std::mutex m;

void add_count() {
// 获得资源
m.lock();
count += 1;
// 释放资源
m.unlock();
}

int main() {
std::thread t1(add_count); // 用于构建线程
std::thread t2(add_count);
t1.join();
t2.join();

std::cout << "Printing count: " << count << std::endl;
return 0;
}

scoped lock

std::scoped_lock 是mutex的包装类,提供了RAII形式的获取和释放lock的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int count = 0;
std::mutex m;

void add_count() {
// The constructor of std::scoped_lock allows for the thread to acquire the
// mutex m.
std::scoped_lock slk(m); // 构建出来的时候表示获取资源
count += 1;

// 到这边的时候这个函数结束,slk析构,释放资源
}

int main() {
std::thread t1(add_count);
std::thread t2(add_count);
t1.join();
t2.join();

std::cout << "Printing count: " << count << std::endl;
return 0;
}

conditional variable

condition variable 允许线程等待一个条件,在满足条件后获取mutex锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int count = 0;
std::mutex m;

// 定义 condition_variable
std::condition_variable cv;

// In this function, a thread increments the count variable by
// 1. It also will notify one waiting thread if the count value is 2.
// It is ran by two of the threads in the main function.
void add_count_and_notify() {
std::scoped_lock slk(m);
count += 1;
if (count == 2) {
cv.notify_one();
}
}


// condition variable 需要一个 std::unique_lock来构建
// unique_lock 是STL形式的同步机制,可移动但是不可拷贝
void waiter_thread() {
std::unique_lock lk(m);
cv.wait(lk, []{return count == 2;});

std::cout << "Printing count: " << count << std::endl;
}

int main() {
std::thread t1(add_count_and_notify);
std::thread t2(add_count_and_notify);
std::thread t3(waiter_thread);
t1.join();
t2.join();
t3.join();
return 0;
}

rwlock

c++没有提供reader-writer锁的相关库,但是可以通过 shared_mutex shared_lock unique_lock 来模拟一个

主要解决的是读写者问题,涉及操作系统相关内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int count = 0;
std::shared_mutex m;

// shared_lock 相当于 读者锁
void read_value() {
std::shared_lock lk(m);
std::cout << "Reading value " + std::to_string(count) + "\n" << std::flush;
}

// unique_lock 相当于 写者锁
void write_value() {
std::unique_lock lk(m);
count += 3;
}

// 都是并行的
int main() {
std::thread t1(read_value);
std::thread t2(write_value);
std::thread t3(read_value);
std::thread t4(read_value);
std::thread t5(write_value);
std::thread t6(read_value);

t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
t6.join();

return 0;
}


作者

Jhuoer Yen

发布于

2024-12-23

更新于

2024-12-23

许可协议

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×