mirror of
https://github.com/changkun/modern-cpp-tutorial.git
synced 2025-12-16 20:27:08 +03:00
book: formatting style updates (#231)
This commit is contained in:
@@ -214,7 +214,8 @@ int main() {
|
||||
}
|
||||
|
||||
// should output: 1, 4, 3, 4. can be simplified using `auto`
|
||||
for (std::vector<int>::iterator element = vec.begin(); element != vec.end(); ++element)
|
||||
for (std::vector<int>::iterator element = vec.begin(); element != vec.end();
|
||||
++element)
|
||||
std::cout << *element << std::endl;
|
||||
}
|
||||
```
|
||||
@@ -301,7 +302,8 @@ int main() {
|
||||
MagicFoo magicFoo = {1, 2, 3, 4, 5};
|
||||
|
||||
std::cout << "magicFoo: ";
|
||||
for (std::vector<int>::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it)
|
||||
for (std::vector<int>::iterator it = magicFoo.vec.begin();
|
||||
it != magicFoo.vec.end(); ++it)
|
||||
std::cout << *it << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -221,8 +221,8 @@ int foo(int a, int b, int c) {
|
||||
;
|
||||
}
|
||||
int main() {
|
||||
// bind parameter 1, 2 on function foo, and use std::placeholders::_1 as placeholder
|
||||
// for the first parameter.
|
||||
// bind parameter 1, 2 on function foo,
|
||||
// and use std::placeholders::_1 as placeholder for the first parameter.
|
||||
auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
|
||||
// when call bindFoo, we only need one param left
|
||||
bindFoo(1);
|
||||
@@ -355,7 +355,8 @@ int main()
|
||||
std::string&& rv1 = std::move(lv1); // legal, std::move can convert lvalue to rvalue
|
||||
std::cout << rv1 << std::endl; // string,
|
||||
|
||||
const std::string& lv2 = lv1 + lv1; // legal, const lvalue reference can extend temp variable's lifecycle
|
||||
const std::string& lv2 = lv1 + lv1; // legal, const lvalue reference can
|
||||
// extend temp variable's lifecycle
|
||||
// lv2 += "Test"; // illegal, const ref can't be modified
|
||||
std::cout << lv2 << std::endl; // string,string,
|
||||
|
||||
@@ -482,8 +483,9 @@ int main() {
|
||||
// "str: Hello world."
|
||||
std::cout << "str: " << str << std::endl;
|
||||
|
||||
// use push_back(const T&&), no copy
|
||||
// the string will be moved to vector, and therefore std::move can reduce copy cost
|
||||
// use push_back(const T&&),
|
||||
// no copy the string will be moved to vector,
|
||||
// and therefore std::move can reduce copy cost
|
||||
v.push_back(std::move(str));
|
||||
// str is empty now
|
||||
std::cout << "str: " << str << std::endl;
|
||||
|
||||
@@ -68,7 +68,7 @@ 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; // 0, pointer2 has reset
|
||||
<< pointer2.use_count() << std::endl; // pointer2 has reset, 0
|
||||
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
|
||||
|
||||
pointer3.reset();
|
||||
@@ -77,7 +77,7 @@ 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; // 0, pointer3 has reset
|
||||
<< pointer3.use_count() << std::endl; // pointer3 has reset, 0
|
||||
```
|
||||
|
||||
## 5.3 `std::unique_ptr`
|
||||
@@ -85,7 +85,7 @@ std::cout << "pointer3.use_count() = "
|
||||
`std::unique_ptr` is an exclusive smart pointer that prohibits other smart pointers from sharing the same object, thus keeping the code safe:
|
||||
|
||||
```cpp
|
||||
std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique was introduced in C++14
|
||||
std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique, from C++14
|
||||
std::unique_ptr<int> pointer2 = pointer; // illegal
|
||||
```
|
||||
|
||||
|
||||
@@ -84,8 +84,9 @@ We use a simple example to briefly introduce the use of this library. Consider t
|
||||
|
||||
int main() {
|
||||
std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
|
||||
// In C++, `\` will be used as an escape character in the string. In order for `\.`
|
||||
// to be passed as a regular expression, it is necessary to perform second escaping of `\`, thus we have `\\.`
|
||||
// In C++, `\` will be used as an escape character in the string.
|
||||
// In order for `\.` to be passed as a regular expression,
|
||||
// it is necessary to perform second escaping of `\`, thus we have `\\.`
|
||||
std::regex txt_regex("[a-z]+\\.txt");
|
||||
for (const auto &fname: fnames)
|
||||
std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
|
||||
@@ -104,7 +105,8 @@ std::smatch base_match;
|
||||
for(const auto &fname: fnames) {
|
||||
if (std::regex_match(fname, base_match, base_regex)) {
|
||||
// the first element of std::smatch matches the entire string
|
||||
// the second element of std::smatch matches the first expression with brackets
|
||||
// the second element of std::smatch matches the first expression
|
||||
// with brackets
|
||||
if (base_match.size() == 2) {
|
||||
std::string base = base_match[1].str();
|
||||
std::cout << "sub-match[0]: " << base_match[0].str() << std::endl;
|
||||
@@ -187,19 +189,23 @@ Please implement the member functions `start()` and `parse_request`. Enable serv
|
||||
template<typename SERVER_TYPE>
|
||||
void start_server(SERVER_TYPE &server) {
|
||||
|
||||
// process GET request for /match/[digit+numbers], e.g.
|
||||
// GET request is /match/abc123, will return abc123
|
||||
server.resource["fill_your_reg_ex"]["GET"] = [](ostream& response, Request& request) {
|
||||
// process GET request for /match/[digit+numbers],
|
||||
// e.g. GET request is /match/abc123, will return abc123
|
||||
server.resource["fill_your_reg_ex"]["GET"] =
|
||||
[](ostream& response, Request& request)
|
||||
{
|
||||
string number=request.path_match[1];
|
||||
response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length()
|
||||
<< "\r\n\r\n" << number;
|
||||
};
|
||||
|
||||
// peocess default GET request; anonymous function will be called if no other matches
|
||||
// response files in folder web/
|
||||
// peocess default GET request;
|
||||
// anonymous function will be called
|
||||
// if no other matches response files in folder web/
|
||||
// default: index.html
|
||||
server.default_resource["fill_your_reg_ex"]["GET"] =
|
||||
[](ostream& response, Request& request) {
|
||||
[](ostream& response, Request& request)
|
||||
{
|
||||
string filename = "www/";
|
||||
|
||||
string path = request.path_match[1];
|
||||
|
||||
@@ -145,7 +145,8 @@ int main() {
|
||||
std::cout << "waiting...";
|
||||
result.wait(); // block until future has arrived
|
||||
// output result
|
||||
std::cout << "done!" << std:: endl << "future result is " << result.get() << std::endl;
|
||||
std::cout << "done!" << std:: endl << "future result is "
|
||||
<< result.get() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
@@ -196,7 +197,8 @@ int main() {
|
||||
// temporal unlock to allow producer produces more rather than
|
||||
// let consumer hold the lock until its consumed.
|
||||
lock.unlock();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // consumer is slower
|
||||
// consumer is slower
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
lock.lock();
|
||||
if (!produced_nums.empty()) {
|
||||
std::cout << "consuming " << produced_nums.front() << std::endl;
|
||||
@@ -272,7 +274,7 @@ This is a very strong set of synchronization conditions, in other words when it
|
||||
This seems too harsh for a variable that requires only atomic operations (no intermediate state).
|
||||
|
||||
The research on synchronization conditions has a very long history, and we will not go into details here. Readers should understand that under the modern CPU architecture, atomic operations at the CPU instruction level are provided.
|
||||
Therefore, in the C++11 multi-threaded shared variable reading and writing, the introduction of the `std::atomic` template, so that we instantiate an atomic type, will be a
|
||||
Therefore, in the C++11 multi-threaded shared variable reading and writing, the introduction of the `std::atomic` template, so that we instantiate an atomic type, will be an
|
||||
Atomic type read and write operations are minimized from a set of instructions to a single CPU instruction. E.g:
|
||||
|
||||
```cpp
|
||||
@@ -417,8 +419,11 @@ Weakening the synchronization conditions between processes, usually we will cons
|
||||
```
|
||||
3 4 4 4 // The write operation of x was quickly observed
|
||||
0 3 3 4 // There is a delay in the observed time of the x write operation
|
||||
0 0 0 4 // The last read read the final value of x, but the previous changes were not observed.
|
||||
0 0 0 0 // The write operation of x is not observed in the current time period, but the situation that x is 4 can be observed at some point in the future.
|
||||
0 0 0 4 // The last read read the final value of x,
|
||||
// but the previous changes were not observed.
|
||||
0 0 0 0 // The write operation of x is not observed in the current time period,
|
||||
// but the situation that x is 4 can be observed
|
||||
// at some point in the future.
|
||||
```
|
||||
|
||||
### Memory Orders
|
||||
@@ -480,9 +485,8 @@ To achieve the ultimate performance and achieve consistency of various strength
|
||||
});
|
||||
std::thread acqrel([&]() {
|
||||
int expected = 1; // must before compare_exchange_strong
|
||||
while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) {
|
||||
while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel))
|
||||
expected = 1; // must after compare_exchange_strong
|
||||
}
|
||||
// flag has changed to 2
|
||||
});
|
||||
std::thread acquire([&]() {
|
||||
|
||||
@@ -176,7 +176,8 @@ int main() {
|
||||
}
|
||||
|
||||
// 将输出 1, 4, 3, 4
|
||||
for (std::vector<int>::iterator element = vec.begin(); element != vec.end(); ++element)
|
||||
for (std::vector<int>::iterator element = vec.begin(); element != vec.end();
|
||||
++element)
|
||||
std::cout << *element << std::endl;
|
||||
}
|
||||
```
|
||||
@@ -228,7 +229,7 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
为了解决这个问题,C++11 首先把初始化列表的概念绑定到了类型上,并将其称之为 `std::initializer_list`,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁,例如:
|
||||
为解决这个问题,C++11 首先把初始化列表的概念绑定到类型上,称其为 `std::initializer_list`,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁,例如:
|
||||
|
||||
```cpp
|
||||
#include <initializer_list>
|
||||
@@ -249,7 +250,9 @@ int main() {
|
||||
MagicFoo magicFoo = {1, 2, 3, 4, 5};
|
||||
|
||||
std::cout << "magicFoo: ";
|
||||
for (std::vector<int>::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) std::cout << *it << std::endl;
|
||||
for (std::vector<int>::iterator it = magicFoo.vec.begin();
|
||||
it != magicFoo.vec.end(); ++it)
|
||||
std::cout << *it << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ Lambda 表达式的本质是一个和函数对象类型相似的类类型(称
|
||||
#include <iostream>
|
||||
|
||||
using foo = void(int); // 定义函数类型, using 的使用见上一节中的别名语法
|
||||
void functional(foo f) { // 定义在参数列表中的函数类型 foo 被视为退化后的函数指针类型 foo*
|
||||
void functional(foo f) { // 参数列表中定义的函数类型 foo 被视为退化后的函数指针类型 foo*
|
||||
f(1); // 通过函数指针调用函数
|
||||
}
|
||||
|
||||
@@ -193,7 +193,8 @@ int foo(int a, int b, int c) {
|
||||
;
|
||||
}
|
||||
int main() {
|
||||
// 将参数1,2绑定到函数 foo 上,但是使用 std::placeholders::_1 来对第一个参数进行占位
|
||||
// 将参数1,2绑定到函数 foo 上,
|
||||
// 但使用 std::placeholders::_1 来对第一个参数进行占位
|
||||
auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
|
||||
// 这时调用 bindFoo 时,只需要提供第一个参数即可
|
||||
bindFoo(1);
|
||||
@@ -551,7 +552,7 @@ constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcep
|
||||
```
|
||||
|
||||
在这份实现中,`std::remove_reference` 的功能是消除类型中的引用,
|
||||
而 `std::is_lvalue_reference` 用于检查类型推导是否正确,在 `std::forward` 的第二个实现中
|
||||
`std::is_lvalue_reference` 则用于检查类型推导是否正确,在 `std::forward` 的第二个实现中
|
||||
检查了接收到的值确实是一个左值,进而体现了坍缩规则。
|
||||
|
||||
当 `std::forward` 接受左值时,`_Tp` 被推导为左值,所以返回值为左值;而当其接受右值时,
|
||||
|
||||
@@ -19,8 +19,8 @@ order: 5
|
||||
也就是我们常说的 RAII 资源获取即初始化技术。
|
||||
|
||||
凡事都有例外,我们总会有需要将对象在自由存储上分配的需求,在传统 C++ 里我们只好使用 `new` 和 `delete` 去
|
||||
『记得』对资源进行释放。而 C++11 引入智能指针的概念,使用引用计数的想法,让程序员不再需要关心手动释放内存。
|
||||
这些智能指针就包括 `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`,使用它们需要包含头文件 `<memory>`。
|
||||
『记得』对资源进行释放。而 C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。
|
||||
这些智能指针包括 `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`,使用它们需要包含头文件 `<memory>`。
|
||||
|
||||
> 注意:引用计数不是垃圾回收,引用计数能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,
|
||||
> 更能够清晰明确的表明资源的生命周期。
|
||||
@@ -67,13 +67,15 @@ std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; //
|
||||
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; // 0, pointer2 已 reset
|
||||
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; // 0, pointer3 已 reset
|
||||
std::cout << "pointer3.use_count() = "
|
||||
<< pointer3.use_count() << std::endl; // pointer3 已 reset; 0
|
||||
```
|
||||
|
||||
## 5.3 `std::unique_ptr`
|
||||
|
||||
@@ -94,7 +94,8 @@ C++11 提供的正则表达式库操作 `std::string` 对象,
|
||||
|
||||
int main() {
|
||||
std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
|
||||
// 在 C++ 中 \ 会被作为字符串内的转义符,为使 \. 作为正则表达式传递进去生效,需要对 \ 进行二次转义,从而有 \\.
|
||||
// 在 C++ 中 \ 会被作为字符串内的转义符,
|
||||
// 为使 \. 作为正则表达式传递进去生效,需要对 \ 进行二次转义,从而有 \\.
|
||||
std::regex txt_regex("[a-z]+\\.txt");
|
||||
for (const auto &fname: fnames)
|
||||
std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
|
||||
@@ -103,7 +104,7 @@ int main() {
|
||||
|
||||
另一种常用的形式就是依次传入 `std::string`/`std::smatch`/`std::regex` 三个参数,
|
||||
其中 `std::smatch` 的本质其实是 `std::match_results`。
|
||||
在标准库中, `std::smatch` 被定义为了 `std::match_results<std::string::const_iterator>`,
|
||||
故而在标准库的实现中, `std::smatch` 被定义为了 `std::match_results<std::string::const_iterator>`,
|
||||
也就是一个子串迭代器类型的 `match_results`。
|
||||
使用 `std::smatch` 可以方便的对匹配的结果进行获取,例如:
|
||||
|
||||
@@ -193,16 +194,23 @@ protected:
|
||||
template<typename SERVER_TYPE>
|
||||
void start_server(SERVER_TYPE &server) {
|
||||
|
||||
// process GET request for /match/[digit+numbers], e.g. GET request is /match/abc123, will return abc123
|
||||
server.resource["fill_your_reg_ex"]["GET"] = [](ostream& response, Request& request) {
|
||||
// process GET request for /match/[digit+numbers],
|
||||
// e.g. GET request is /match/abc123, will return abc123
|
||||
server.resource["fill_your_reg_ex"]["GET"] =
|
||||
[](ostream& response, Request& request)
|
||||
{
|
||||
string number=request.path_match[1];
|
||||
response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
|
||||
response << "HTTP/1.1 200 OK\r\nContent-Length: "
|
||||
<< number.length() << "\r\n\r\n" << number;
|
||||
};
|
||||
|
||||
// peocess default GET request; anonymous function will be called if no other matches
|
||||
// response files in folder web/
|
||||
// peocess default GET request;
|
||||
// anonymous function will be called
|
||||
// if no other matches response files in folder web/
|
||||
// default: index.html
|
||||
server.default_resource["fill_your_reg_ex"]["GET"] = [](ostream& response, Request& request) {
|
||||
server.default_resource["fill_your_reg_ex"]["GET"] =
|
||||
[](ostream& response, Request& request)
|
||||
{
|
||||
string filename = "www/";
|
||||
|
||||
string path = request.path_match[1];
|
||||
|
||||
@@ -70,7 +70,7 @@ int main() {
|
||||
由于 C++ 保证了所有栈对象在生命周期结束时会被销毁,所以这样的代码也是异常安全的。
|
||||
无论 `critical_section()` 正常返回、还是在中途抛出异常,都会引发堆栈回退,也就自动调用了 `unlock()`。
|
||||
|
||||
而 `std::unique_lock` 则相对于 `std::lock_guard` 出现的,`std::unique_lock` 更加灵活,
|
||||
而 `std::unique_lock` 则是相对于 `std::lock_guard` 出现的,`std::unique_lock` 更加灵活,
|
||||
`std::unique_lock` 的对象会以独占所有权(没有其他的 `unique_lock` 对象同时拥有某个 `mutex` 对象的所有权)
|
||||
的方式管理 `mutex` 对象上的上锁和解锁的操作。所以在并发编程中,推荐使用 `std::unique_lock`。
|
||||
|
||||
@@ -147,7 +147,8 @@ int main() {
|
||||
std::cout << "waiting...";
|
||||
result.wait(); // 在此设置屏障,阻塞到期物的完成
|
||||
// 输出执行结果
|
||||
std::cout << "done!" << std:: endl << "future result is " << result.get() << std::endl;
|
||||
std::cout << "done!" << std:: endl << "future result is "
|
||||
<< result.get() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
@@ -198,7 +199,8 @@ int main() {
|
||||
}
|
||||
// 短暂取消锁,使得生产者有机会在消费者消费空前继续生产
|
||||
lock.unlock();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 消费者慢于生产者
|
||||
// 消费者慢于生产者
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
lock.lock();
|
||||
while (!produced_nums.empty()) {
|
||||
std::cout << "consuming " << produced_nums.front() << std::endl;
|
||||
@@ -277,7 +279,7 @@ int main() {
|
||||
这是一组非常强的同步条件,换句话说当最终编译为 CPU 指令时会表现为非常多的指令(我们之后再来看如何实现一个简单的互斥锁)。
|
||||
这对于一个仅需原子级操作(没有中间态)的变量,似乎太苛刻了。
|
||||
|
||||
关于同步条件的研究有着非常久远的历史,我们在这里不进行赘述。读者应该明白,在现代 CPU 体系结构下提供了 CPU 指令级的原子操作,
|
||||
关于同步条件的研究有着非常久远的历史,我们在这里不进行赘述。读者应该明白,现代 CPU 体系结构提供了 CPU 指令级的原子操作,
|
||||
因此在 C++11 中多线程下共享变量的读写这一问题上,还引入了 `std::atomic` 模板,使得我们实例化一个原子类型,将一个
|
||||
原子类型读写操作从一组指令,最小化到单个 CPU 指令。例如:
|
||||
|
||||
@@ -311,7 +313,7 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
当然,并非所有的类型都能提供原子操作,这是因为原子操作的可行性取决于 CPU 的架构以及所实例化的类型结构是否满足该架构对内存对齐
|
||||
当然,并非所有的类型都能提供原子操作,这是因为原子操作的可行性取决于具体的 CPU 架构,以及所实例化的类型结构是否能够满足该 CPU 架构对内存对齐
|
||||
条件的要求,因而我们总是可以通过 `std::atomic<T>::is_lock_free` 来检查该原子类型是否需支持原子操作,例如:
|
||||
|
||||
```cpp
|
||||
@@ -419,7 +421,7 @@ int main() {
|
||||
|
||||
|
||||
T2 ---------+------------+--------------------+--------+-------->
|
||||
x.read() x.read() x.read() x.read()
|
||||
x.read x.read() x.read() x.read()
|
||||
```
|
||||
|
||||
在上面的情况中,如果我们假设 x 的初始值为 0,则 `T2` 中四次 `x.read()` 结果可能但不限于以下情况:
|
||||
@@ -428,7 +430,8 @@ int main() {
|
||||
3 4 4 4 // x 的写操作被很快观察到
|
||||
0 3 3 4 // x 的写操作被观察到的时间存在一定延迟
|
||||
0 0 0 4 // 最后一次读操作读到了 x 的最终值,但此前的变化并未观察到
|
||||
0 0 0 0 // 在当前时间段内 x 的写操作均未被观察到,但未来某个时间点上一定能观察到 x 为 4 的情况
|
||||
0 0 0 0 // 在当前时间段内 x 的写操作均未被观察到,
|
||||
// 但未来某个时间点上一定能观察到 x 为 4 的情况
|
||||
```
|
||||
|
||||
### 内存顺序
|
||||
@@ -489,9 +492,8 @@ int main() {
|
||||
});
|
||||
std::thread acqrel([&]() {
|
||||
int expected = 1; // must before compare_exchange_strong
|
||||
while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) {
|
||||
while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel))
|
||||
expected = 1; // must after compare_exchange_strong
|
||||
}
|
||||
// flag has changed to 2
|
||||
});
|
||||
std::thread acquire([&]() {
|
||||
@@ -553,7 +555,7 @@ C++11 语言层提供了并发编程的相关支持,本节简单的介绍了 `
|
||||
|
||||
## 进一步阅读的参考资料
|
||||
|
||||
- [C++ 并发编程\(中文版\)](https://www.gitbook.com/book/chenxiaowei/cpp_concurrency_in_action/details)
|
||||
- [C++ 并发编程\(中文版\)](https://book.douban.com/subject/26386925/)
|
||||
- [线程支持库文档](https://en.cppreference.com/w/cpp/thread)
|
||||
- Herlihy, M. P., & Wing, J. M. (1990). Linearizability: a correctness condition for concurrent objects. ACM Transactions on Programming Languages and Systems, 12(3), 463–492. https://doi.org/10.1145/78969.78972
|
||||
|
||||
|
||||
Reference in New Issue
Block a user