mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2025-03-24 08:10:19 +08:00
修改部分书写错误及格式.
This commit is contained in:
parent
e53571b75c
commit
84b0d44f4f
@ -8,7 +8,7 @@
|
||||
|
||||
即使显式支持了移动操作,结果可能也没有你希望的那么好。比如,所有C++11的标准库都支持了移动操作,但是认为移动所有容器的开销都非常小是个错误。对于某些容器来说,压根就不存在开销小的方式来移动它所包含的内容。对另一些容器来说,开销真正小的移动操作却使得容器元素移动含义事与愿违。
|
||||
|
||||
考虑一下`std::array`,这是C++11中的新容器。`std::array`本质上是具有STL接口的内置数组。这与其他标准容器将内容存储在堆内存不同。存储具体数据在堆内存的容器,本身只保存了只想堆内存数据的指针(真正实现当然更复杂一些,但是基本逻辑就是这样)。这种实现使得在常数时间移动整个容器成为可能的,只需要拷贝容器中保存的指针到目标容器,然后将原容器的指针置为空指针就可以了。
|
||||
考虑一下`std::array`,这是C++11中的新容器。`std::array`本质上是具有STL接口的内置数组。这与其他标准容器将内容存储在堆内存不同。存储具体数据在堆内存的容器,本身只保存了指向堆内存数据的指针(真正实现当然更复杂一些,但是基本逻辑就是这样)。这种实现使得在常数时间移动整个容器成为可能的,只需要拷贝容器中保存的指针到目标容器,然后将原容器的指针置为空指针就可以了。
|
||||
|
||||
```cpp
|
||||
std::vector<Widget> vm1;
|
||||
@ -26,7 +26,7 @@ auto aw2 = std::move(aw1); // move aw1 into aw2. Runs in linear time. All elemen
|
||||
|
||||
注意`aw1`中的元素被移动到了`aw2`中,这里假定`Widget`类的移动操作比复制操作快。但是使用`std::array`的移动操作还是复制操作都将花费线性时间的开销,因为每个容器中的元素终归需要拷贝一次,这与“移动一个容器就像操作几个指针一样方便”的含义想去甚远。
|
||||
|
||||
另一方面,`std::strnig`提供了常数时间的移动操作和线性时间的复制操作。这听起来移动比复制快多了,但是可能不一定。许多字符串的实现采用了*small string optimization(SSO)*。"small"字符串(比如长度小于15个字符的)存储在了`std::string`的缓冲区中,并没有存储在堆内存,移动这种存储的字符串并不必复制操作更快。
|
||||
另一方面,`std::strnig`提供了常数时间的移动操作和线性时间的复制操作。这听起来移动比复制快多了,但是可能不一定。许多字符串的实现采用了*small string optimization (SSO)*。"small"字符串(比如长度小于15个字符的)存储在了`std::string`的缓冲区中,并没有存储在堆内存,移动这种存储的字符串并不必复制操作更快。
|
||||
|
||||
SSO的动机是大量证据表明,短字符串是大量应用使用的习惯。使用内存缓冲区存储而不分配堆内存空间,是为了更好的效率。然而这种内存管理的效率导致移动的效率并不必复制操作高。
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
## Item30:熟悉完美转发的失败case
|
||||
## Item30:熟悉完美转发的失败 case
|
||||
|
||||
C++11最显眼的功能之一就是完美转发功能。完美转发,太棒了!哎,开始使用,你就发现“完美”,理想与现实还是有差距。C++11的完美转发是非常好用,但是只有当你愿意忽略一些失败情况,这个Item就是使你熟悉这些情形。
|
||||
C++11最显眼的功能之一就是完美转发功能。完美转发,太棒了!哎,开始使用,你就发现“完美”,理想与现实还是有差距。C++11 的完美转发是非常好用,但是只有当你愿意忽略一些失败情况,这个 Item 就是使你熟悉这些情形。
|
||||
|
||||
在我们开始epsilon探索之前,有必要回顾一下“完美转发”的含义。“转发”仅表示将一个函数的参数传递给另一个函数。对于被传递的第二个函数目标是收到与第一个函数完全相同的对象。这就排除了按值传递参数,因为它们是原始调用者传入内容的副本。我们希望被转发的函数能够可以与原始函数一起使用对象。指着参数也被排除在外,因为我们不想强迫调用者传入指针。关于通用转发,我们将处理引用参数。
|
||||
在我们开始 epsilon 探索之前,有必要回顾一下“完美转发”的含义。“转发”仅表示将一个函数的参数传递给另一个函数。对于被传递的第二个函数目标是收到与第一个函数完全相同的对象。这就排除了按值传递参数,因为它们是原始调用者传入内容的副本。我们希望被转发的函数能够可以与原始函数一起使用对象。指针参数也被排除在外,因为我们不想强迫调用者传入指针。关于通用转发,我们将处理引用参数。
|
||||
|
||||
完美转发意味着我们不仅转发对象,我们还转发显著的特征:它们的类型,是左值还是右值,是const还是volatile。结合到我们会处理引用参数,这意味着我们将使用通用引用(参见Item24),因为通用引用参数被传入参数时才确定是左值还是右值。
|
||||
完美转发意味着我们不仅转发对象,我们还转发显著的特征:它们的类型,是左值还是右值,是 `const` 还是 `volatile`。结合到我们会处理引用参数,这意味着我们将使用通用引用(参见Item24),因为通用引用参数被传入参数时才确定是左值还是右值。
|
||||
|
||||
假定我们有一些函数f,然后想编写一个转发给它的函数(就使用一个函数模板)。我们需要的核心看起来像是这样:
|
||||
|
||||
@ -16,7 +16,7 @@ void fwd(T&& param) // accept any argument
|
||||
}
|
||||
```
|
||||
|
||||
从本质上说,转发功能是通用的。例如fwd模板,接受任何类型的采纳数,并转发得到的任何参数。这种通用性的逻辑扩展是转发函数不仅是模板,而且是可变模板,因此可以接受任何数量的参数。fwd的可变个是如下:
|
||||
从本质上说,转发功能是通用的。例如 `fwd` 模板,接受任何类型的参数,并转发得到的任何参数。这种通用性的逻辑扩展是转发函数不仅是模板,而且是可变模板,因此可以接受任何数量的参数。fwd的可变个时如下:
|
||||
|
||||
```cpp
|
||||
template<typename... Ts>
|
||||
@ -26,7 +26,7 @@ void fwd(Ts&&... params) // accept any arguments
|
||||
}
|
||||
```
|
||||
|
||||
这种形式你会在标准化容器emplace中(参见Item42)和只能容器的工厂函数`std::make_unique和std::make_shared`中(参见Item21)看到。
|
||||
这种形式你会在标准化容器emplace中(参见 Item42)和智能指针的工厂函数`std::make_unique` 和 `std::make_shared`中(参见 Item21)看到。
|
||||
|
||||
给定我们的目标函数f和被转发的函数fwd,如果f使用特定参数做一件事,但是fwd使用相同的参数做另一件事,完美转发就会失败:
|
||||
|
||||
@ -37,7 +37,7 @@ fwd(expression); // but this does something else, fwd fails to perfectly forward
|
||||
|
||||
导致这种失败的原因有很多。知道它们是什么以及如何解决它们很重要,因此让我们来看看那种参数无法做到完美转发。
|
||||
|
||||
### Braced initializers(支撑初始化器)
|
||||
### Braced initializers(花括号初始化器)
|
||||
|
||||
假定f这样声明:
|
||||
|
||||
@ -59,12 +59,12 @@ fwd({1,2,3}); // error! doesn't compile
|
||||
|
||||
这是因为这是完美转发失效的一种情况。
|
||||
|
||||
所有这种错误有相同的原因。在对f的直接调用(例如f({1,2,3})),编译器看到传入的参数是声明中的类型。如果类型不匹配,就会执行隐式转换操作使得调用成功。在上面的例子中,从`{1,2,3}`生成了临时变量`std::vector<int>`对象,因此f的参数会绑定到`std::vector<int>`对象上。
|
||||
所有这种错误有相同的原因。在对f的直接调用(例如 `f({1,2,3})`),编译器看到传入的参数是声明中的类型。如果类型不匹配,就会执行隐式转换操作使得调用成功。在上面的例子中,从`{1,2,3}`生成了临时变量`std::vector<int>`对象,因此f的参数会绑定到`std::vector<int>`对象上。
|
||||
|
||||
当通过调用函数模板fwd调用f时,编译器不再比较传入给fwd的参数和f的声明中参数的类型。代替的是,推导传入给fwd的参数类型,然后比较推导后的参数类型和f的声明类型。当下面情况任何一个发生时,完美转发就会失败:
|
||||
当通过调用函数模板 `fwd` 调用f时,编译器不再比较传入给 `fwd` 的参数和f的声明中参数的类型。代替的是,推导传入给fwd的参数类型,然后比较推导后的参数类型和f的声明类型。当下面情况任何一个发生时,完美转发就会失败:
|
||||
|
||||
- **编译器不能推导出一个或者多个fwd的参数类型**,编译器就会报错
|
||||
- **编译器将一个或者多个fwd的参数类型推导错误**。在这里,“错误”可能意味着fwd将无法使用推导出的类型进行编译,但是也可能意味着调用者f使用fwd的推导类型对比直接传入参数类型表现出不一致的行为。这种不同行为的原因可能是因为f的函数重载定义,并且由于是“不正确的”类型推导,在fwd内部调用f和直接调用f将重载不同的函数。
|
||||
- **编译器不能推导出一个或者多个 `fwd` 的参数类型**,编译器就会报错
|
||||
- **编译器将一个或者多个 `fwd` 的参数类型推导错误**。在这里,“错误”可能意味着fwd将无法使用推导出的类型进行编译,但是也可能意味着调用者f使用fwd的推导类型对比直接传入参数类型表现出不一致的行为。这种不同行为的原因可能是因为f的函数重载定义,并且由于是“不正确的”类型推导,在fwd内部调用f和直接调用f将重载不同的函数。
|
||||
|
||||
在上面的`f({1,2,3})`例子中,问题在于,如标准所言,将括号初始化器传递给未声明为`std::initializer_list`的函数模板参数,该标准规定为“非推导上下文”。简单来讲,这意味着编译器在对fwd的调用中推导表达式`{1,2,3}`的类型,因为fwd的参数没有声明为`std::initializer_list`。对于fwd参数的推导类型被阻止,编译器只能拒绝该调用。
|
||||
|
||||
@ -75,13 +75,13 @@ auto il = {1,2,3}; // il's type deduced to be std::initializer_list<int>
|
||||
fwd(il); // fine, perfect-forwards il to f
|
||||
```
|
||||
|
||||
### 0或者NULL作为空指针
|
||||
### 0 或者 `NULL` 作为空指针
|
||||
|
||||
Item8说明当你试图传递0或者NULL作为空指针给模板时,类型推导会出错,推导为一个整数类型而不是指针类型。结果就是不管是0还是NULL都不能被完美转发为空指针。解决方法非常简单,使用nullptr就可以了,具体的细节,参考Item 8.
|
||||
Item8说明当你试图传递 0 或者 `NULL` 作为空指针给模板时,类型推导会出错,推导为一个整数类型而不是指针类型。结果就是不管是0还是NULL都不能被完美转发为空指针。解决方法非常简单,使用 `nullptr` 就可以了,具体的细节可参考Item 8.
|
||||
|
||||
### 仅声明的整数静态const数据成员
|
||||
### 仅声明的整数静态 `const` 数据成员
|
||||
|
||||
通常,无需在类中定义整数静态const数据成员;声明就可以了。这是因为编译器会对此类成员
|
||||
通常,无需在类中定义整数静态const数据成员;声明就可以了。这是因为编译器会对此类成员进行常量传播 (const propagation), 而不需要为它们开辟内存. 例如考虑下面的代码:
|
||||
|
||||
```cpp
|
||||
class Widget {
|
||||
@ -96,7 +96,7 @@ widgetData.reserve(Widget::MinVals); // use of MinVals
|
||||
|
||||
这里,我们使用`Widget::MinVals`(或者简单点MinVals)来确定`widgetData`的初始容量,即使`MinVals`缺少定义。编译器通过将值28放入所有位置来补充缺少的定义。没有为`MinVals`的值留存储空间是没有问题的。如果要使用`MinVals`的地址(例如,有人创建了`MinVals`的指针),则`MinVals`需要存储(因为指针总要有一个地址),尽管上面的代码仍然可以编译,但是链接时就会报错,直到为`MinVals`提供定义。
|
||||
|
||||
按照这个思路,想象下f(转发参数给fwd的函数)这样声明:
|
||||
按照这个思路,想象下f(转发参数给 `fwd` 的函数)这样声明:
|
||||
|
||||
```cpp
|
||||
void f(std::size_t val);
|
||||
@ -155,9 +155,9 @@ int processVal(int value, int priority);
|
||||
f(processVal); // fine
|
||||
```
|
||||
|
||||
但是有一点要注意,f要求一个函数指针,但是`processVal`不是一个函数指针或者一个函数,它是两个同名的函数。但是,编译器可以知道它需要哪个:通过参数类型和数量来匹配。因此选择了一个int参数的`processVal`地址传递给f
|
||||
但是有一点要注意,f要求一个函数指针,但是`processVal`不是一个函数指针或者一个函数,它是两个同名的函数。但是,编译器可以知道它需要哪个:通过参数类型和数量来匹配。因此选择了一个int参数的`processVal`地址传递给`f`。
|
||||
|
||||
工作的基本机制是让编译器帮选择f的声明选择一个需要的`processVal`。但是,fwd是一个函数模板,没有需要的类型信息,使得编译器不可能帮助自动匹配一个合适的函数:
|
||||
工作的基本机制是让编译器帮选择f的声明选择一个需要的`processVal`。但是,`fwd`是一个函数模板,没有需要的类型信息,使得编译器不可能帮助自动匹配一个合适的函数:
|
||||
|
||||
```cpp
|
||||
fwd(processVal); // error! which processVal?
|
||||
@ -173,7 +173,7 @@ T workOnVal(T param) { ... } // template for processing values
|
||||
fwd(workOnVal); // error! which workOnVal instantiation ?
|
||||
```
|
||||
|
||||
获得像fwd的完美转发接受一个重载函数名或者模板函数名的方式是指定转发的类型。比如,你可以创造与f相同参数类型的函数指针,通过processVal或者workOnVal实例化这个函数指针(可以引导生成代码时正确选择函数实例),然后传递指针给f:
|
||||
获得像`fwd`的完美转发接受一个重载函数名或者模板函数名的方式是指定转发的类型。比如,你可以创造与f相同参数类型的函数指针,通过`processVal`或者`workOnVal`实例化这个函数指针(可以引导生成代码时正确选择函数实例),然后传递指针给f:
|
||||
|
||||
```cpp
|
||||
using ProcessFuncType = int (*)(int); // make typedef; see Item 9
|
||||
@ -182,7 +182,7 @@ fwd(processValPtr); // fine
|
||||
fwd(static_cast<ProcessFuncType>(workOnVal)); // alse fine
|
||||
```
|
||||
|
||||
当然,这要求你知道fwd转发的函数指针的类型。对于完美转发来说这一点并不合理,毕竟,完美转发被设计为转发任何内容,如果没有文档告诉你转发的类型,你如何知道?(译者注:这里应该想表达,这是解决重载函数名或者函数模板的解决方案,但是这是完美转发本身的问题)
|
||||
当然,这要求你知道fwd转发的函数指针的类型。对于完美转发来说这一点并不合理,毕竟,完美转发被设计为转发任何内容,如果没有文档告诉你转发的类型,你如何知道?(译者注:这里应该想表达,这是解决重载函数名或者函数模板的解决方案,但是这是完美转发本身的问题。)
|
||||
|
||||
### 位域
|
||||
|
||||
@ -191,15 +191,15 @@ fwd(static_cast<ProcessFuncType>(workOnVal)); // alse fine
|
||||
```cpp
|
||||
struct IPv4Header {
|
||||
std::uint32_t version:4,
|
||||
IHL:4,
|
||||
DSCP:6,
|
||||
ECN:2,
|
||||
totalLength:16;
|
||||
IHL:4,
|
||||
DSCP:6,
|
||||
ECN:2,
|
||||
totalLength:16;
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
如果声明我们的函数f(转发函数fwd的目标)为接收一个`std::size_t`的参数,则使用IPv4Header对象的totalLength字段进行调用没有问题:
|
||||
如果声明我们的函数`f`(转发函数fwd的目标)为接收一个`std::size_t`的参数,则使用`IPv4Header`对象的`totalLength`字段进行调用没有问题:
|
||||
|
||||
```cpp
|
||||
void f(std::size_t sz);
|
||||
@ -208,17 +208,17 @@ IPv4Header h;
|
||||
f(h.totalLength);// fine
|
||||
```
|
||||
|
||||
如果通过fwd转发h.totalLength给f呢,那就是一个不同的情况了:
|
||||
如果通过 `fwd` 转发 `h.totalLength` 给 `f` 呢,那就是一个不同的情况了:
|
||||
|
||||
```cpp
|
||||
fwd(h.totalLength); // error!
|
||||
```
|
||||
|
||||
问题在于fwd的参数是引用,而h.totalLength是非常量位域。听起来并不是那么糟糕,但是C++标准非常清楚地谴责了这种组合:非常量引用不应该绑定到位域。禁止的理由很充分。位域可能包含了机器字节的任意部分(比如32位int的3-5位),但是无法直接定位。我之前提到了在硬件层面引用和指针时一样的,所以没有办法创建一个指向任意bit的指针(C++规定你可以指向的最小单位是char),所以就没有办法绑定引用到任意bit上。
|
||||
问题在于 `fwd` 的参数是引用,而 `h.totalLength` 是非常量位域。听起来并不是那么糟糕,但是C++标准非常清楚地谴责了这种组合:非常量引用不应该绑定到位域。禁止的理由很充分。位域可能包含了机器字节的任意部分(比如 32 位 `int` 的 3 - 5 位),但是无法直接定位。我之前提到了在硬件层面引用和指针时一样的,所以没有办法创建一个指向任意bit的指针(C++规定你可以指向的最小单位是`char`),所以就没有办法绑定引用到任意 bit 上。
|
||||
|
||||
一旦意识到接收位域作为参数的函数都将接收位域的副本,就可以轻松解决位域不能完美转发的问题。毕竟,没有函数可以绑定引用到位域,也没有函数可以接受指向位域的指针(不存在这种指针)。这种位域类型的参数只能按值传递,或者有趣的事,常量引用也可以。在按值传递时,被调用的函数接受了一个位域的副本,而且事实表明,位域的常量引用也是将其“复制”到普通对象再传递。
|
||||
|
||||
传递位域给完美转发的关键就是利用接收参数函数接受的是一个副本的事实。你可以自己创建副本然后利用副本调用完美转发。在IPv4Header的例子中,可以如下写法:
|
||||
传递位域给完美转发的关键就是利用接收参数函数接受的是一个副本的事实。你可以自己创建副本然后利用副本调用完美转发。在 `IPv4Header` 的例子中,可以如下写法:
|
||||
|
||||
```cpp
|
||||
// copy bitfield value; see Item6 for info on init. form
|
||||
@ -228,10 +228,9 @@ fwd(length); // forward the copy
|
||||
|
||||
### 总结
|
||||
|
||||
在大多数情况下,完美转发工作的很好。你基本不用考虑其他问题。但是当其不工作时,当看起来合理的代码无法编译,或者更糟的是,无法按照预期运行时,了解完美转发的缺陷就很重要了。同样重要的是如何解决它们。在大多数情况下,都很简单
|
||||
在大多数情况下,完美转发工作的很好。你基本不用考虑其他问题。但是当其不工作时,当看起来合理的代码无法编译,或者更糟的是,无法按照预期运行时,了解完美转发的缺陷就很重要了。同样重要的是如何解决它们。在大多数情况下都很简单。
|
||||
|
||||
### 需要记住的事
|
||||
|
||||
- 完美转发会失败当模板类型推导失败或者推导类型错误
|
||||
- 导致完美转发失败的类型有braced initializers,作为空指针的0或者NULL,只声明的整型static const数据成员,模板和重载的函数名,位域
|
||||
|
||||
- 完美转发会失败当模板类型推导失败或者推导类型错误。
|
||||
- 导致完美转发失败的类型有 braced initializers,作为空指针的 0 或者 `NULL`,只声明 (而未定义) 的整型 static const 数据成员,模板和重载的函数名和位域。
|
@ -1,6 +1,6 @@
|
||||
C++11的伟大标志之一是将并发整合到语言和库中。熟悉其他线程API(比如pthreads或者Windows threads)的开发者有时可能会对C++提供的斯巴达式(译者注:应该是简陋和严谨的意思)功能集感到惊讶,这是因为C++对于并发的大量支持是在编译器的约束层面。由此产生的语言保证意味着在C++的历史中,开发者首次通过标准库可以写出跨平台的多线程程序。这位构建表达库奠定了坚实的基础,并发标准库(tasks, futures, threads, mutexes, condition variables, atomic objects等)仅仅是成为并发软件开发者丰富工具集的基础。
|
||||
|
||||
在接下来的Item中,记住标准库有两个futures的模板:`std::future和std::shared_future`。在许多情况下,区别不重要,所以我们经常简单的混于一谈为*futures*。
|
||||
在接下来的Item中,记住标准库有两个futures的模板:`std::future` 和 `std::shared_future`。在许多情况下,区别不重要,所以我们经常简单的混于一谈为*futures*。
|
||||
|
||||
# 优先基于任务编程而不是基于线程
|
||||
|
||||
@ -15,13 +15,13 @@ auto fut = std::async(doAsyncWork); // "fut" for "future"
|
||||
```
|
||||
这种方式中,函数对象作为一个任务传递给 `std::async`。
|
||||
|
||||
基于任务的方法通常比基于线程的方法更优,原因之一上面的代码已经表明,基于任务的方法代码量更少。我们假设唤醒`doAsyncWork`的代码对于其提供的返回值是有需求的。基于线程的方法对此无能为力,而基于任务的方法可以简单地获取`std::async`返回的`future`提供的`get`函数获取这个返回值。如果`doAsycnWork`发生了异常,`get`函数就显得更为重要,因为`get`函数可以提供抛出异常的访问,而基于线程的方法,如果`doAsyncWork`抛出了异常,线程会直接终止(通过调用`std::terminate`)。
|
||||
基于任务的方法通常比基于线程的方法更优,原因之一上面的代码已经表明,基于任务的方法代码量更少。我们假设唤醒 `doAsyncWork` 的代码对于其提供的返回值是有需求的。基于线程的方法对此无能为力,而基于任务的方法可以简单地获取`std::async`返回的`future`提供的`get`函数获取这个返回值。如果`doAsycnWork`发生了异常,`get`函数就显得更为重要,因为`get`函数可以提供抛出异常的访问,而基于线程的方法,如果`doAsyncWork`抛出了异常,线程会直接终止(通过调用`std::terminate`)。
|
||||
|
||||
基于线程与基于任务最根本的区别在于抽象层次的高低。基于任务的方式使得开发者从线程管理的细节中解放出来,对此在C++并发软件中总结了'thread'的三种含义:
|
||||
|
||||
- 硬件线程(Hardware threads)是真实执行计算的线程。现代计算机体系结构为每个CPU核心提供一个或者多个硬件线程。
|
||||
- 软件线程(Software threads)(也被称为系统线程)是操作系统管理的在硬件线程上执行的线程。通常可以存在比硬件线程更多数量的软件线程,因为当软件线程被比如 I/O、同步锁或者条件变量阻塞的时候,操作系统可以调度其他未阻塞的软件线程执行提供吞吐量。
|
||||
- `std::threads`是C++执行过程的对象,并作为软件线程的handle(句柄)。`std::threads`存在多种状态,1. `null`表示空句柄,因为处于默认构造状态(即没有函数来执行),因此不对应任何软件线程。 2. moved from (moved-to的`std::thread`就对应软件进程开始执行) 3. `joined`(连接唤醒与被唤醒的两个线程) 4. `detached`(将两个连接的线程分离)
|
||||
- `std::thread` 是C++执行过程的对象,并作为软件线程的句柄 (handle)。`std::thread` 存在多种状态,1. `null`表示空句柄,因为处于默认构造状态(即没有函数来执行),因此不对应任何软件线程。 2. moved from (moved-to的`std::thread` 就对应软件进程开始执行) 3. `joined`(连接唤醒与被唤醒的两个线程) 4. `detached`(将两个连接的线程分离)
|
||||
|
||||
软件线程是有限的资源。如果开发者试图创建大于系统支持的硬件线程数量,会抛出`std::system_error`异常。即使你编写了不抛出异常的代码,这仍然会发生,比如下面的代码,即使 `doAsyncWork`是 `noexcept`
|
||||
```cpp
|
||||
@ -43,11 +43,11 @@ std::thread t(doAsyncWork); // throw if no more
|
||||
|
||||
```cpp
|
||||
auto fut = std::async(doAsyncWork); // onus of thread mgmt is
|
||||
// on implement of
|
||||
// the Standard Library
|
||||
// on implement of
|
||||
// the Standard Library
|
||||
```
|
||||
|
||||
这种调用方式将线程管理的职责转交给C++标准库的开发者。举个例子,这种调用方式会减少抛出资源超额的异常,为何这么说调用`std::async`并不保证开启一个新的线程,只是提供了执行函数的保证,具体是否创建新的线程来运行此函数,取决于具体实现,比如可以通过调度程序来将`AsyncWork`运行在等待此函数结果的线程上,调度程序的合理性决定了系统是否会抛出资源超额的异常,但是这是库开发者需要考虑的事情了。
|
||||
这种调用方式将线程管理的职责转交给C++标准库的开发者。举个例子,这种调用方式会减少抛出资源超额的异常,为何这么说? 调用 `std::async` 并不保证开启一个新的线程,只是提供了执行函数的保证,具体是否创建新的线程来运行此函数,取决于具体实现,比如可以通过调度程序来将`AsyncWork`运行在等待此函数结果的线程上,调度程序的合理性决定了系统是否会抛出资源超额的异常,但是这是库开发者需要考虑的事情了。
|
||||
|
||||
如果考虑自己实现在等待结果的线程上运行输出结果的函数,之前提到了可能引出负载不均衡的问题,`std::async`运行时的调度程序显然比开发者更清楚调度策略的制定,因为运行时调度程序管理的是所有执行过程,而不仅仅个别开发者运行的代码。
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user