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

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