book: formatting style updates (#231)

This commit is contained in:
ReEcho
2022-07-17 19:29:30 +08:00
committed by GitHub
parent 41b88c861e
commit e50e3c64f2
10 changed files with 104 additions and 74 deletions

View File

@@ -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;
}
```

View File

@@ -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;

View File

@@ -57,27 +57,27 @@ And see the reference count of an object by `use_count()`. E.g:
auto pointer = std::make_shared<int>(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<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
```

View File

@@ -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<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) {
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--;
}
// (...)
};

View File

@@ -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([&]() {

View File

@@ -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;
}
```

View File

@@ -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` 被推导为左值,所以返回值为左值;而当其接受右值时,

View File

@@ -19,15 +19,15 @@ 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>`
> 注意:引用计数不是垃圾回收,引用计数能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,
> 更能够清晰明确的表明资源的生命周期。
## 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<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
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`

View File

@@ -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];

View File

@@ -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), 463492. https://doi.org/10.1145/78969.78972