Merge pull request #62 from MistEO/master

fix typos
This commit is contained in:
kelthuzadx 2020-12-15 11:07:55 +08:00 committed by GitHub
commit 6ade27f0aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 11 additions and 10 deletions

View File

@ -91,13 +91,14 @@ private:
我希望这段代码是不言自明的,但是下面几点说明可能会有所帮助:
- 构造器只接受`std::thread`右值因为我们想要move`std::thread`对象给`ThreadRAII`(再次强调,`std::thread`不可以复制)
- 构造器只接受`std::thread`右值因为我们想要move `std::thread`对象给`ThreadRAII`(再次强调,`std::thread`不可以复制)
- 构造器的参数顺序设计的符合调用者直觉(首先传递`std::thread`,然后选择析构执行的动作),但是成员初始化列表设计的匹配成员声明的顺序。将`std::thread`成员放在声明最后。在这个类中,这个顺序没什么特别之处,调整为其他顺序也没有问题,但是通常,可能一个成员的初始化依赖于另一个,因为`std::thread`对象可能会在初始化结束后就立即执行了,所以在最后声明是一个好习惯。这样就能保证一旦构造结束,所有数据成员都初始化完毕可以安全的异步绑定线程执行
- `ThreadRAII`提供了`get`函数访问内部的`std::thread`对象。这类似于标准智能指针提供的`get`函数,可以提供访问原始指针的入口。提供`get`函数避免了`ThreadRAII`复制完整`std::thread`接口的需要,因为着`ThreadRAII`可以在需要`std::thread`上下文的环境中使用
- 在`ThreadRAII`析构函数调用`std::thread`对象t的成员函数之前检查t是否joinable。这是必须的因为在unjoinbale的`std::thread`上调用`join or detach`会导致未定义行为。客户端可能会构造一个`std::thread`t然后通过t构造一个`ThreadRAII`,使用`get`获取t然后移动t或者调用`join or detach`每一个操作都使得t变为unjoinable
- 在`ThreadRAII`析构函数调用`std::thread`对象t的成员函数之前检查t是否joinable。这是必须的因为在unjoinbale的`std::thread`上调用`join or detach`会导致未定义行为。客户端可能会构造一个`std::thread` t然后通过t构造一个`ThreadRAII`,使用`get`获取t然后移动t或者调用`join or detach`每一个操作都使得t变为unjoinable
如果你担心下面这段代码
```cpp

View File

@ -120,7 +120,7 @@ y = x; // read x again
编译器可通过忽略对y的一次赋值来优化代码因为初始化和赋值是冗余的。
正常内存还有一个特征,就是如果你写入内存没就不会读,再次吸入,第一次写就可以被忽略,因为肯定会被覆盖。给出下面的代码:
正常内存还有一个特征,就是如果你写入内存没有读取,再次写入,第一次写就可以被忽略,因为肯定会被覆盖。给出下面的代码:
```cpp
x = 10; // write x
@ -143,7 +143,7 @@ auto y = x;
x = 20;
```
可能你会想会写这种重复读写的代码技术上称为redundant loads 和 dead stores答案是开发者不会直接写至少我们不希望开发者这样写。但是在编译器执行了模板实例化内联和一系列重排序优化之后结果会出现多余的操作和无效存储所以编译器需要摆脱这样的情况并不少见。
可能你会想会写这种重复读写的代码技术上称为redundant loads 和 dead stores答案是开发者不会直接写至少我们不希望开发者这样写。但是在编译器执行了模板实例化内联和一系列重排序优化之后结果会出现多余的操作和无效存储所以编译器需要摆脱这样的情况并不少见。
这种有话讲仅仅在内存表现正常时有效。“特殊”的内存不行。最常见的“特殊”内存是用来memory-mapped I/O的内存。这种内存实际上是与外围设备比如外部传感器或者显示器打印机网络端口通信而不是读写比如RAM。这种情况下再次考虑多余的代码

View File

@ -71,13 +71,13 @@ vs.push_back(queenOfDisco); // copy-construct queenOfDisco
vs.emplace_back(queenOfDisco); // ditto
```
因此emplacement函数可以完成insertion函数的所有功能。并且有时效率更高在理论上,不会更低效。那为什么不在所有场合使用它们?
因此emplacement函数可以完成insertion函数的所有功能。并且有时效率更高在理论上,不会更低效。那为什么不在所有场合使用它们?
因为就像说的那样理论上在理论和实际上没有什么区别但是实际区别还是有的。在当前标准库的实现下有些场景就像预期的那样emplacement执行性能优于insertion但是有些场景反而insertion更快。这种场景不容易描述因为依赖于传递的参数类型、容器类型、emplacement或insertion的容器位置、容器类型构造器的异常安全性和对于禁止重复值的容器即`std::set,std::map,std::unorder_set,set::unorder_map`要添加的值是否已经在容器中。因此大致的调用建议是通过benchmakr测试来确定emplacment和insertion哪种更快。
当然这个结论不是很令人满意所以还有一种启发式的方法来帮助你确定是否应该使用emplacement。如果下列条件都能满足emplacement会优于insertion
- **值是通过构造器添加到容器,而不是直接赋值。**例子就像本Item刚开始的那样添加"xyzzy"到`std::string的std::vector`中)。新值必须通过`std::string`的构造器添加到`std::vector`。如果我们回看这个例子,新值放到已经存在对象的位置,那情况就完全不一样了。考虑下:
- **值是通过构造器添加到容器,而不是直接赋值。** 例子就像本Item刚开始的那样添加"xyzzy"到`std::string的std::vector`中)。新值必须通过`std::string`的构造器添加到`std::vector`。如果我们回看这个例子,新值放到已经存在对象的位置,那情况就完全不一样了。考虑下:
```cpp
std::vector<std::string> vs; // as before
@ -89,9 +89,9 @@ vs.emplace_back(queenOfDisco); // ditto
而且,向容器添加元素是通过构造还是赋值通常取决于实现者。但是,启发式仍然是有帮助的。基于节点的容器实际上总是使用构造器添加新元素,大多数标准库容器都是基于节点的。例外的容器只有`std::vector, std::deque, std::string``std::array`也不是基于节点的但是它不支持emplacement和insertion。在不是基于节点的容器中你可以依靠`emplace_back`来使用构造向容器添加元素,对于`std::deque``emplace_front`也是一样的。
- **传递的参数类型与容器的初始化类型不同。**再次强调emplacement优于insertion通常基于以下事实当传递的参数不是容器保存的类型时接口不需要创建和销毁临时对象。当将类型为T的对象添加到container<T>没有理由期望emplacement比insertion运行的更快因为不需要创建临时对象来满足insertion接口。
- **传递的参数类型与容器的初始化类型不同。** 再次强调emplacement优于insertion通常基于以下事实当传递的参数不是容器保存的类型时接口不需要创建和销毁临时对象。当将类型为T的对象添加到container<T>没有理由期望emplacement比insertion运行的更快因为不需要创建临时对象来满足insertion接口。
- **容器不拒绝重复项作为新值。**这意味着容器要么允许添加重复值要么你添加的元素都是不重复的。这样要求的原因是为了判断一个元素是否已经存在于容器中emplacement实现通常会创建一个具有新值的节点以便可以将该节点的值与现有容器中节点的值进行比较。如果要添加的值不在容器中则链接该节点。然后如果值已经存在emplacement创建的节点就会被销毁意味着构造和析构时浪费的开销。这样的创建就不会在insertion函数中出现。
- **容器不拒绝重复项作为新值。** 这意味着容器要么允许添加重复值要么你添加的元素都是不重复的。这样要求的原因是为了判断一个元素是否已经存在于容器中emplacement实现通常会创建一个具有新值的节点以便可以将该节点的值与现有容器中节点的值进行比较。如果要添加的值不在容器中则链接该节点。然后如果值已经存在emplacement创建的节点就会被销毁意味着构造和析构时浪费的开销。这样的创建就不会在insertion函数中出现。
本Item开始的例子中下面的调用满足上面的条件。所以调用比`push_back`运行更快。
@ -130,7 +130,7 @@ ptrs.push_back({new Widget, killWidget});
`std::shared_ptr`的临时对象创建应该可以避免,但是在这个场景下,临时对象值得被创建。考虑如下可能的时间序列:
1. 在上述的调用中,一个`std::shared_ptr<Widget> `的临时对象被创建来持有`new Widget`对象。称这个对象为*temp*。
1. 在上述的调用中,一个`std::shared_ptr<Widget>`的临时对象被创建来持有`new Widget`对象。称这个对象为*temp*。
2. `push_back`接受*temp*的引用。在节点的分配一个副本来复制*temp*的过程中OOM异常被抛出
3. 随着异常从`push_back`的传播,*temp*被销毁。作为唯一管理Widget的弱指针`std::shared_ptr`对象,会自动销毁`Widget`,在这里就是调用`killWidget`。
@ -221,7 +221,7 @@ std::regex r2(nullptr); // compiles
在标准的官方术语中用于初始化r1的语法是所谓的复制初始化。相反用于初始化r2的语法是也被称为braces被称为直接初始化。复制初始化不是显式调用构造器的直接初始化是。这就是r2可以编译的原因。
然后回到`push_back和 emplace_back`更一般来说insertion函数对比emplacment函数。emplacement函数使用直接初始化这意味着使用显式构造器。insertion函数使用复制初始化。因此
然后回到`push_back和emplace_back`更一般来说insertion函数对比emplacment函数。emplacement函数使用直接初始化这意味着使用显式构造器。insertion函数使用复制初始化。因此
```cpp
regexes.emplace_back(nullptr); // compiles. Direct init permits use of explicit std::regex ctor taking a pointer