diff --git a/book/en-us/02-usability.md b/book/en-us/02-usability.md index 79a754f..619b49b 100644 --- a/book/en-us/02-usability.md +++ b/book/en-us/02-usability.md @@ -214,7 +214,8 @@ int main() { } // should output: 1, 4, 3, 4. can be simplified using `auto` - for (std::vector::iterator element = vec.begin(); element != vec.end(); ++element) + for (std::vector::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::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) + for (std::vector::iterator it = magicFoo.vec.begin(); + it != magicFoo.vec.end(); ++it) std::cout << *it << std::endl; } ``` diff --git a/book/en-us/03-runtime.md b/book/en-us/03-runtime.md index e12e608..8c7c145 100644 --- a/book/en-us/03-runtime.md +++ b/book/en-us/03-runtime.md @@ -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; diff --git a/book/en-us/05-pointers.md b/book/en-us/05-pointers.md index 38c167c..dab6df9 100644 --- a/book/en-us/05-pointers.md +++ b/book/en-us/05-pointers.md @@ -57,27 +57,27 @@ And see the reference count of an object by `use_count()`. E.g: auto pointer = std::make_shared(10); auto pointer2 = pointer; // reference count+1 auto pointer3 = pointer; // reference count+1 -int *p = pointer.get(); // no increase of reference count +int *p = pointer.get(); // no increase of reference count -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 +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 << "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(); std::cout << "reset pointer3:" << std::endl; -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1 +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 pointer = std::make_unique(10); // make_unique was introduced in C++14 +std::unique_ptr pointer = std::make_unique(10); // make_unique, from C++14 std::unique_ptr pointer2 = pointer; // illegal ``` diff --git a/book/en-us/06-regex.md b/book/en-us/06-regex.md index 1ac999e..e8b9854 100644 --- a/book/en-us/06-regex.md +++ b/book/en-us/06-regex.md @@ -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,32 +189,36 @@ Please implement the member functions `start()` and `parse_request`. Enable serv template 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) { - string filename = "www/"; + [](ostream& response, Request& request) + { + string filename = "www/"; - string path = request.path_match[1]; + string path = request.path_match[1]; - // forbidden use `..` access content outside folder web/ - size_t last_pos = path.rfind("."); - size_t current_pos = 0; - size_t pos; - while((pos=path.find('.', current_pos)) != string::npos && pos != last_pos) { - current_pos = pos; - path.erase(pos, 1); - last_pos--; - } + // forbidden use `..` access content outside folder web/ + size_t last_pos = path.rfind("."); + size_t current_pos = 0; + size_t pos; + while((pos=path.find('.', current_pos)) != string::npos && pos != last_pos) { + current_pos = pos; + path.erase(pos, 1); + last_pos--; + } // (...) }; diff --git a/book/en-us/07-thread.md b/book/en-us/07-thread.md index 359c6cb..379d4d2 100644 --- a/book/en-us/07-thread.md +++ b/book/en-us/07-thread.md @@ -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([&]() { diff --git a/book/zh-cn/02-usability.md b/book/zh-cn/02-usability.md index 8b1731c..51a8b0d 100644 --- a/book/zh-cn/02-usability.md +++ b/book/zh-cn/02-usability.md @@ -176,7 +176,8 @@ int main() { } // 将输出 1, 4, 3, 4 - for (std::vector::iterator element = vec.begin(); element != vec.end(); ++element) + for (std::vector::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 @@ -249,7 +250,9 @@ int main() { MagicFoo magicFoo = {1, 2, 3, 4, 5}; std::cout << "magicFoo: "; - for (std::vector::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) std::cout << *it << std::endl; + for (std::vector::iterator it = magicFoo.vec.begin(); + it != magicFoo.vec.end(); ++it) + std::cout << *it << std::endl; } ``` diff --git a/book/zh-cn/03-runtime.md b/book/zh-cn/03-runtime.md index 10f82ca..57839ba 100644 --- a/book/zh-cn/03-runtime.md +++ b/book/zh-cn/03-runtime.md @@ -136,7 +136,7 @@ Lambda 表达式的本质是一个和函数对象类型相似的类类型(称 #include 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` 被推导为左值,所以返回值为左值;而当其接受右值时, diff --git a/book/zh-cn/05-pointers.md b/book/zh-cn/05-pointers.md index b88428a..ef7467f 100644 --- a/book/zh-cn/05-pointers.md +++ b/book/zh-cn/05-pointers.md @@ -19,15 +19,15 @@ order: 5 也就是我们常说的 RAII 资源获取即初始化技术。 凡事都有例外,我们总会有需要将对象在自由存储上分配的需求,在传统 C++ 里我们只好使用 `new` 和 `delete` 去 -『记得』对资源进行释放。而 C++11 引入智能指针的概念,使用引用计数的想法,让程序员不再需要关心手动释放内存。 -这些智能指针就包括 `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`,使用它们需要包含头文件 ``。 +『记得』对资源进行释放。而 C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。 +这些智能指针包括 `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`,使用它们需要包含头文件 ``。 > 注意:引用计数不是垃圾回收,引用计数能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待, > 更能够清晰明确的表明资源的生命周期。 ## 5.2 `std::shared_ptr` -`std::shared_ptr` 是一种智能指针,它能够记录多少个 `shared_ptr` 共同指向一个对象,从而消除显式的调用 +`std::shared_ptr` 是一种智能指针,它能够记录多少个 `shared_ptr` 共同指向一个对象,从而消除显式的调用 `delete`,当引用计数变为零的时候就会将对象自动删除。 但还不够,因为使用 `std::shared_ptr` 仍然需要使用 `new` 来调用,这使得代码出现了某种程度上的不对称。 @@ -59,21 +59,23 @@ int main() { auto pointer = std::make_shared(10); auto pointer2 = pointer; // 引用计数+1 auto pointer3 = pointer; // 引用计数+1 -int *p = pointer.get(); // 这样不会增加引用计数 -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 +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; // 0, pointer2 已 reset +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 << "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` diff --git a/book/zh-cn/06-regex.md b/book/zh-cn/06-regex.md index b24da50..a4c6597 100644 --- a/book/zh-cn/06-regex.md +++ b/book/zh-cn/06-regex.md @@ -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::smatch` 被定义为了 `std::match_results`, 也就是一个子串迭代器类型的 `match_results`。 使用 `std::smatch` 可以方便的对匹配的结果进行获取,例如: @@ -193,16 +194,23 @@ protected: template 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]; diff --git a/book/zh-cn/07-thread.md b/book/zh-cn/07-thread.md index ea99ed0..d6b08fd 100644 --- a/book/zh-cn/07-thread.md +++ b/book/zh-cn/07-thread.md @@ -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::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