From bdf0e899a7f9619acba31f8c5eee15bb2d6c6324 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Tue, 21 May 2024 05:53:15 +0800 Subject: [PATCH 1/8] book: fix imprecise description of `NULL` in C++ (#279) --- book/en-us/02-usability.md | 17 +++++++++-------- book/zh-cn/02-usability.md | 4 ++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/book/en-us/02-usability.md b/book/en-us/02-usability.md index 6da7d8e5..51d3edcf 100644 --- a/book/en-us/02-usability.md +++ b/book/en-us/02-usability.md @@ -18,20 +18,21 @@ which refers to the language behavior that occurred before the runtime. ### nullptr -The purpose of `nullptr` appears to replace `NULL`. In a sense, -traditional C++ treats `NULL` and `0` as the same thing, -depending on how the compiler defines NULL, -and some compilers define NULL as `((void*)0)` Some will define it directly as `0`. +The purpose of `nullptr` appears to replace `NULL`. There are **null pointer constants** in the C and C++ languages, +which can be implicitly converted to null pointer value of any pointer type, +or null member pointer value of any pointer-to-member type in C++. +`NULL` is provided by the standard library implementation and defined as an implementation-defined null pointer constant. +In C, some standard libraries defines `NULL` as `((void*)0)` and some define it as `0`. -C++ **does not allow** to implicitly convert `void *` to other types. -But if the compiler tries to define `NULL` as `((void*)0)`, then in the following code: +C++ **does not allow** to implicitly convert `void *` to other types, and thus `((void*)0)` is not a valid implementation +of `NULL`. If the standard library tries to define `NULL` as `((void*)0)`, then compilation error would occur in the following code: ```cpp char *ch = NULL; ``` C++ without the `void *` implicit conversion has to define `NULL` as `0`. -This still creates a new problem. Defining `NULL` to 0 will cause the overloading feature in `C++` to be confusing. +This still creates a new problem. Defining `NULL` to `0` will cause the overloading feature in `C++` to be confusing. Consider the following two `foo` functions: ```cpp @@ -41,7 +42,7 @@ void foo(int); Then the `foo(NULL);` statement will call `foo(int)`, which will cause the code to be counterintuitive. -To solve this problem, C++11 introduced the `nullptr` keyword, which is specifically used to distinguish null pointers, 0. The type of `nullptr` is `nullptr_t`, which can be implicitly converted to any pointer or member pointer type, and can be compared equally or unequally with them. +To solve this problem, C++11 introduced the `nullptr` keyword, which is specifically used to distinguish null pointers, `0`. The type of `nullptr` is `nullptr_t`, which can be implicitly converted to any pointer or member pointer type, and can be compared equally or unequally with them. You can try to compile the following code using clang++: diff --git a/book/zh-cn/02-usability.md b/book/zh-cn/02-usability.md index ed4fe982..11047e7e 100644 --- a/book/zh-cn/02-usability.md +++ b/book/zh-cn/02-usability.md @@ -14,9 +14,9 @@ order: 2 ### nullptr -`nullptr` 出现的目的是为了替代 `NULL`。在某种意义上来说,传统 C++ 会把 `NULL`、`0` 视为同一种东西,这取决于编译器如何定义 `NULL`,有些编译器会将 `NULL` 定义为 `((void*)0)`,有些则会直接将其定义为 `0`。 +`nullptr` 出现的目的是为了替代 `NULL`。 C 与 C++ 语言中有**空指针常量**,它们能被隐式转换成任何指针类型的空指针值,或 C++ 中的任何成员指针类型的空成员指针值。 `NULL` 由标准库实现提供,并被定义为实现定义的空指针常量。在 C 中,有些标准库会把 `NULL` 定义为 `((void*)0)` 而有些将它定义为 `0`。 -C++ **不允许**直接将 `void *` 隐式转换到其他类型。但如果编译器尝试把 `NULL` 定义为 `((void*)0)`,那么在下面这句代码中: +C++ **不允许**直接将 `void *` 隐式转换到其他类型,从而 `((void*)0)` 不是 `NULL` 的合法实现。如果标准库尝试把 `NULL` 定义为 `((void*)0)`,那么下面这句代码中会出现编译错误: ```cpp char *ch = NULL; From 54e99e14617a17c7c6f9eb7936d77e449b0e20ce Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Tue, 21 May 2024 05:54:46 +0800 Subject: [PATCH 2/8] book: avoid "let the compiler deduce the list by itself" (#277) --- book/en-us/03-runtime.md | 4 ++-- book/zh-cn/03-runtime.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/book/en-us/03-runtime.md b/book/en-us/03-runtime.md index a61d6654..8ecd2956 100644 --- a/book/en-us/03-runtime.md +++ b/book/en-us/03-runtime.md @@ -87,8 +87,8 @@ capture lists can be: - \[\] empty capture list - \[name1, name2, ...\] captures a series of variables -- \[&\] reference capture, let the compiler deduce the reference list by itself -- \[=\] value capture, let the compiler deduce the value list by itself +- \[&\] reference capture, determine the reference capture list from the uses the in function body +- \[=\] value capture, determine the value capture list from the uses in the function body #### 4. Expression capture diff --git a/book/zh-cn/03-runtime.md b/book/zh-cn/03-runtime.md index 1760b3a8..94517dc7 100644 --- a/book/zh-cn/03-runtime.md +++ b/book/zh-cn/03-runtime.md @@ -76,8 +76,8 @@ void lambda_reference_capture() { - \[\] 空捕获列表 - \[name1, name2, ...\] 捕获一系列变量 -- \[&\] 引用捕获, 让编译器自行推导引用列表 -- \[=\] 值捕获, 让编译器自行推导值捕获列表 +- \[&\] 引用捕获, 从函数体内的使用确定引用捕获列表 +- \[=\] 值捕获, 从函数体内的使用确定值捕获列表 #### 4. 表达式捕获 From 5c50dbc591b045e3633fbe957c7702e483664566 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Tue, 21 May 2024 05:56:11 +0800 Subject: [PATCH 3/8] book: avoid implication that `volatile` may be usable in concurrency (#281) --- book/en-us/07-thread.md | 2 +- book/zh-cn/07-thread.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/book/en-us/07-thread.md b/book/en-us/07-thread.md index 379d4d2a..14f74062 100644 --- a/book/en-us/07-thread.md +++ b/book/en-us/07-thread.md @@ -228,7 +228,7 @@ We simply can't expect multiple consumers to be able to produce content in a par ## 7.5 Atomic Operation and Memory Model Careful readers may be tempted by the fact that the example of the producer-consumer model in the previous section may have compiler optimizations that cause program errors. -For example, the boolean `notified` is not modified by `volatile`, and the compiler may have optimizations for this variable, such as the value of a register. +For example, the compiler may have optimizations for the variable `notified`, such as the value of a register. As a result, the consumer thread can never observe the change of this value. This is a good question. To explain this problem, we need to further discuss the concept of the memory model introduced from C++11. Let's first look at a question. What is the output of the following code? ```cpp diff --git a/book/zh-cn/07-thread.md b/book/zh-cn/07-thread.md index 8e11407d..c32a3c6b 100644 --- a/book/zh-cn/07-thread.md +++ b/book/zh-cn/07-thread.md @@ -232,7 +232,7 @@ int main() { ## 7.5 原子操作与内存模型 细心的读者可能会对前一小节中生产者消费者模型的例子可能存在编译器优化导致程序出错的情况产生疑惑。 -例如,布尔值 `notified` 没有被 `volatile` 修饰,编译器可能对此变量存在优化,例如将其作为一个寄存器的值, +例如,编译器可能对变量 `notified` 存在优化,例如将其作为一个寄存器的值, 从而导致消费者线程永远无法观察到此值的变化。这是一个好问题,为了解释清楚这个问题,我们需要进一步讨论 从 C++ 11 起引入的内存模型这一概念。我们首先来看一个问题,下面这段代码输出结果是多少? @@ -242,7 +242,7 @@ int main() { int main() { int a = 0; - int flag = 0; + volatile int flag = 0; std::thread t1([&]() { while (flag != 1); From 78b1cdf6bb454a7ed067fc2e81faa719a847935d Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Tue, 21 May 2024 05:58:17 +0800 Subject: [PATCH 4/8] book: correct description of function template deduction from `auto` (#280) --- book/en-us/02-usability.md | 16 +++++++++++----- book/zh-cn/02-usability.md | 17 +++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/book/en-us/02-usability.md b/book/en-us/02-usability.md index 51d3edcf..eb8a21aa 100644 --- a/book/en-us/02-usability.md +++ b/book/en-us/02-usability.md @@ -418,17 +418,23 @@ auto i = 5; // i as int auto arr = new auto(10); // arr as int * ``` -Since C++ 20, `auto` can even be used as function arguments. Consider -the following example: +Since C++ 14, `auto` can even be used as function arguments in generic lambda expressions, +and such functionality is generalized to normal functions in C++ 20. +Consider the following example: ```cpp -int add(auto x, auto y) { +auto add14 = [](auto x, auto y) -> int { + return x+y; +} + +int add20(auto x, auto y) { return x+y; } auto i = 5; // type int auto j = 6; // type int -std::cout << add(i, j) << std::endl; +std::cout << add14(i, j) << std::endl; +std::cout << add20(i, j) << std::endl; ``` > **Note**: `auto` cannot be used to derive array types yet: @@ -481,7 +487,7 @@ type z == type x ### tail type inference -You may think that when we introduce `auto`, we have already mentioned that `auto` cannot be used for function arguments for type derivation. Can `auto` be used to derive the return type of a function? Still consider an example of an add function, which we have to write in traditional C++: +You may think that whether `auto` can be used to deduce the return type of a function. Still consider an example of an add function, which we have to write in traditional C++: ```cpp template diff --git a/book/zh-cn/02-usability.md b/book/zh-cn/02-usability.md index 11047e7e..c39f60a7 100644 --- a/book/zh-cn/02-usability.md +++ b/book/zh-cn/02-usability.md @@ -354,17 +354,22 @@ auto i = 5; // i 被推导为 int auto arr = new auto(10); // arr 被推导为 int * ``` -从 C++ 20 起,`auto` 甚至能用于函数传参,考虑下面的例子: +从 C++ 14 起,`auto` 能用于 lambda 表达式中的函数传参,而 C++ 20 起该功能推广到了一般的函数。考虑下面的例子: ```cpp -int add(auto x, auto y) { +auto add14 = [](auto x, auto y) -> int { return x+y; } -auto i = 5; // 被推导为 int -auto j = 6; // 被推导为 int -std::cout << add(i, j) << std::endl; +int add20(auto x, auto y) { + return x+y; +} + +auto i = 5; // type int +auto j = 6; // type int +std::cout << add14(i, j) << std::endl; +std::cout << add20(i, j) << std::endl; ``` > @@ -413,7 +418,7 @@ type z == type x ### 尾返回类型推导 -你可能会思考,在介绍 `auto` 时,我们已经提过 `auto` 不能用于函数形参进行类型推导,那么 `auto` 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写: +你可能会思考, `auto` 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写: ```cpp template From af45678ad42ddad195c954a5dd7ad80398440175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Tue, 21 May 2024 13:14:12 +0800 Subject: [PATCH 5/8] book: revise wording about "stack unwinding" --- book/en-us/07-thread.md | 4 +++- book/zh-cn/07-thread.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/book/en-us/07-thread.md b/book/en-us/07-thread.md index 14f74062..8173aefd 100644 --- a/book/en-us/07-thread.md +++ b/book/en-us/07-thread.md @@ -67,7 +67,9 @@ int main() { ``` Because C++ guarantees that all stack objects will be destroyed at the end of the declaration period, such code is also extremely safe. -Whether `critical_section()` returns normally or if an exception is thrown in the middle, a stack rollback is thrown, and `unlock()` is automatically called. +Whether `critical_section()` returns normally or if an exception is thrown in the middle, a stack unwinding is thrown, and `unlock()` is automatically called. + +> An exception is thrown and not caught (it is implementation-defined whether any stack unwinding is done in this case). `std::unique_lock` is more flexible than `std::lock_guard`. Objects of `std::unique_lock` manage the locking and unlocking operations on the `mutex` object with exclusive ownership (no other `unique_lock` objects owning the ownership of a `mutex` object). So in concurrent programming, it is recommended to use `std::unique_lock`. diff --git a/book/zh-cn/07-thread.md b/book/zh-cn/07-thread.md index c32a3c6b..ba80e7e5 100644 --- a/book/zh-cn/07-thread.md +++ b/book/zh-cn/07-thread.md @@ -68,7 +68,9 @@ int main() { ``` 由于 C++ 保证了所有栈对象在生命周期结束时会被销毁,所以这样的代码也是异常安全的。 -无论 `critical_section()` 正常返回、还是在中途抛出异常,都会引发堆栈回退,也就自动调用了 `unlock()`。 +无论 `critical_section()` 正常返回、还是在中途抛出异常,都会引发栈回溯,也就自动调用了 `unlock()`。 + +> 没有捕获抛出的异常(此时由实现定义是否进行栈回溯)。 而 `std::unique_lock` 则是相对于 `std::lock_guard` 出现的,`std::unique_lock` 更加灵活, `std::unique_lock` 的对象会以独占所有权(没有其他的 `unique_lock` 对象同时拥有某个 `mutex` 对象的所有权) From 2659a5f2073d7a96224567a9ebd0d5e5c266f6ba Mon Sep 17 00:00:00 2001 From: Shaobo Liu Date: Sat, 1 Jun 2024 18:08:51 +0800 Subject: [PATCH 6/8] book: add missing result (#291) Co-authored-by: Shaobo Liu --- book/zh-cn/02-usability.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/zh-cn/02-usability.md b/book/zh-cn/02-usability.md index c39f60a7..e9c84e9a 100644 --- a/book/zh-cn/02-usability.md +++ b/book/zh-cn/02-usability.md @@ -963,7 +963,7 @@ enum class new_enum : unsigned int { ```cpp if (new_enum::value3 == new_enum::value4) { - // 会输出 + // 会输出true std::cout << "new_enum::value3 == new_enum::value4" << std::endl; } ``` From 7b57e5e5a10a5707d278ffd4172815821202a6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mq=E7=99=BD?= <3326284481@qq.com> Date: Sat, 1 Jun 2024 18:09:32 +0800 Subject: [PATCH 7/8] book: revise the description of RAII (#288) --- book/en-us/07-thread.md | 2 +- book/zh-cn/07-thread.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/book/en-us/07-thread.md b/book/en-us/07-thread.md index 8173aefd..2279efdc 100644 --- a/book/en-us/07-thread.md +++ b/book/en-us/07-thread.md @@ -35,7 +35,7 @@ C++11 introduces a class related to `mutex`, with all related functions in the ` It can be locked by its member function `lock()`, and `unlock()` can be unlocked. But in the process of actually writing the code, it is best not to directly call the member function, Because calling member functions, you need to call `unlock()` at the exit of each critical section, and of course, exceptions. -At this time, C++11 also provides a template class `std::lock_guard` for the RAII syntax for the mutex. +At this time, C++11 also provides a template class `std::lock_guard` for the RAII mechanism for the mutex. RAII guarantees the exceptional security of the code while keeping the simplicity of the code. diff --git a/book/zh-cn/07-thread.md b/book/zh-cn/07-thread.md index ba80e7e5..dbb35085 100644 --- a/book/zh-cn/07-thread.md +++ b/book/zh-cn/07-thread.md @@ -35,7 +35,8 @@ C++11 引入了 `mutex` 相关的类,其所有相关的函数都放在 ` Date: Sat, 1 Jun 2024 18:10:55 +0800 Subject: [PATCH 8/8] book: revise usage of "mutex" and "instantiation" (#292) --- book/en-us/07-thread.md | 8 ++++---- book/zh-cn/07-thread.md | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/book/en-us/07-thread.md b/book/en-us/07-thread.md index 2279efdc..a9f41162 100644 --- a/book/en-us/07-thread.md +++ b/book/en-us/07-thread.md @@ -31,7 +31,7 @@ int main() { We have already learned the basics of concurrency technology in the operating system, or the database, and `mutex` is one of the cores. C++11 introduces a class related to `mutex`, with all related functions in the `` header file. -`std::mutex` is the most basic `mutex` class in C++11, and you can create a mutex by instantiating `std::mutex`. +`std::mutex` is the most basic mutex class in C++11, and a mutex can be created by constructing a `std::mutex` object. It can be locked by its member function `lock()`, and `unlock()` can be unlocked. But in the process of actually writing the code, it is best not to directly call the member function, Because calling member functions, you need to call `unlock()` at the exit of each critical section, and of course, exceptions. @@ -160,7 +160,7 @@ After encapsulating the target to be called, you can use `get_future()` to get a The condition variable `std::condition_variable` was born to solve the deadlock and was introduced when the mutex operation was not enough. For example, a thread may need to wait for a condition to be true to continue execution. A dead wait loop can cause all other threads to fail to enter the critical section so that when the condition is true, a deadlock occurs. -Therefore, the `condition_variable` instance is created primarily to wake up the waiting thread and avoid deadlocks. +Therefore, the `condition_variable` object is created primarily to wake up the waiting thread and avoid deadlocks. `notify_one()` of `std::condition_variable` is used to wake up a thread; `notify_all()` is to notify all threads. Below is an example of a producer and consumer model: @@ -276,8 +276,8 @@ 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 an -Atomic type read and write operations are minimized from a set of instructions to a single CPU instruction. E.g: +Therefore, the `std::atomic` template is introduced in C++11 for the topic of multi-threaded shared variable reading and writing, which enables us to instantiate atomic types, +and minimize an atomic read or write operation from a set of instructions to a single CPU instruction. E.g: ```cpp std::atomic counter; diff --git a/book/zh-cn/07-thread.md b/book/zh-cn/07-thread.md index dbb35085..3c59a72d 100644 --- a/book/zh-cn/07-thread.md +++ b/book/zh-cn/07-thread.md @@ -31,7 +31,7 @@ int main() { 我们在操作系统、亦或是数据库的相关知识中已经了解过了有关并发技术的基本知识,`mutex` 就是其中的核心之一。 C++11 引入了 `mutex` 相关的类,其所有相关的函数都放在 `` 头文件中。 -`std::mutex` 是 C++11 中最基本的 `mutex` 类,通过实例化 `std::mutex` 可以创建互斥量, +`std::mutex` 是 C++11 中最基本的互斥量类,可以通过构造 `std::mutex` 对象创建互斥量, 而通过其成员函数 `lock()` 可以进行上锁,`unlock()` 可以进行解锁。 但是在实际编写代码的过程中,最好不去直接调用成员函数, 因为调用成员函数就需要在每个临界区的出口处调用 `unlock()`,当然,还包括异常。 @@ -163,7 +163,7 @@ int main() { 条件变量 `std::condition_variable` 是为了解决死锁而生,当互斥操作不够用而引入的。 比如,线程可能需要等待某个条件为真才能继续执行, 而一个忙等待循环中可能会导致所有其他线程都无法进入临界区使得条件为真时,就会发生死锁。 -所以,`condition_variable` 实例被创建出现主要就是用于唤醒等待线程从而避免死锁。 +所以,`condition_variable` 对象被创建出现主要就是用于唤醒等待线程从而避免死锁。 `std::condition_variable`的 `notify_one()` 用于唤醒一个线程; `notify_all()` 则是通知所有线程。下面是一个生产者和消费者模型的例子: @@ -283,8 +283,7 @@ int main() { 这对于一个仅需原子级操作(没有中间态)的变量,似乎太苛刻了。 关于同步条件的研究有着非常久远的历史,我们在这里不进行赘述。读者应该明白,现代 CPU 体系结构提供了 CPU 指令级的原子操作, -因此在 C++11 中多线程下共享变量的读写这一问题上,还引入了 `std::atomic` 模板,使得我们实例化一个原子类型,将一个 -原子类型读写操作从一组指令,最小化到单个 CPU 指令。例如: +因此在多线程下共享变量的读写这一问题上, C++11 中还引入了 `std::atomic` 模板,使得我们能实例化原子类型,并将一个原子写操作从一组指令,最小化到单个 CPU 指令。例如: ```cpp std::atomic counter;