mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2025-01-14 22:30:25 +08:00
Modify some translation.
This commit is contained in:
parent
133fdd7cbc
commit
b21d092c1b
@ -33,7 +33,7 @@ std::array<int, arraySize2> data2; // 没问题, arraySize2是constexpr
|
||||
+ **constexpr**函数可以用于需求编译期常量的上下文。如果你传给**constexpr**函数的实参在编译期可知,那么结果将在编译期计算。如果实参的值在编译期不知道,你的代码就会被拒绝。
|
||||
+ 当一个**constexpr**函数被一个或者多个编译期不可知值调用时,它就像普通函数一样,运行时计算它的结果。这意味着你不需要两个函数,一个用于编译期计算,一个用于运行时计算。**constexpr**全做了。
|
||||
|
||||
假设我们需要一个数据结构来存储一个实验的结果,而这个实验可能以各种方式进行。实验期间风扇转速,温度等等都可能导致亮度值改变,亮度值可以是高,低,或者无。如果有n个实验相关的环境条件。它们每一个都有三个状态,最终可以得到的组合$$3^n$$个。储存所有实验结果的所有组合需要这个数据结构足够大。假设每个结果都是**int**并且**n**是编译期已知的(或者可以被计算出的),一个`std::array`是一个合理的选择。我们需要一个方法在编译期计算`3^n`。C++标准库提供了`std::pow`,它的数学意义正是我们所需要的,但是,对我们来说,这里还有两个问题。第一,`std::pow`是为浮点类型设计的 我们需要整型结果。第二,`std::pow`不是**constexpr**(即,使用编译期可知值调用得到的可能不是编译期可知的结果),所以我们不能用它作为`std::array`的大小。
|
||||
假设我们需要一个数据结构来存储一个实验的结果,而这个实验可能以各种方式进行。实验期间风扇转速,温度等等都可能导致亮度值改变,亮度值可以是高,低,或者无。如果有n个实验相关的环境条件。它们每一个都有三个状态,最终可以得到的组合`3^n`个。储存所有实验结果的所有组合需要这个数据结构足够大。假设每个结果都是**int**并且**n**是编译期已知的(或者可以被计算出的),一个`std::array`是一个合理的选择。我们需要一个方法在编译期计算`3^n`。C++标准库提供了`std::pow`,它的数学意义正是我们所需要的,但是,对我们来说,这里还有两个问题。第一,`std::pow`是为浮点类型设计的 我们需要整型结果。第二,`std::pow`不是**constexpr**(即,使用编译期可知值调用得到的可能不是编译期可知的结果),所以我们不能用它作为`std::array`的大小。
|
||||
|
||||
幸运的是,我们可以应需写个`pow`。我将展示怎么快速完成它,不过现在让我们先看看它应该怎么被声明和使用:
|
||||
```cpp
|
||||
|
@ -81,7 +81,7 @@ private:
|
||||
`std::mutex m`被声明为`mutable`,因为锁定和解锁它的都是non-const函数。在`roots`(const成员函数)中,`m`将被视为const对象。
|
||||
值得注意的是,因为`std::mutex`是一种`move-only`的类型(一种可以移动但不能复制的类型),所以将`m`添加进多项式中的副作用是使它失去了被复制的能力。不过,它仍然可以移动。
|
||||
|
||||
在某些情况下,互斥量是过度的(?)。例如,你所做的只是计算成员函数被调用了多少次。使用`std::atomic` 修饰的counter(保证其他线程视这个操作为不可分割的发生,参见item40)。(然而它是否轻量取决于你使用的硬件和标准库中互斥量的实现。)以下是如何使用`std::atomic`来统计调用次数。
|
||||
在某些情况下,互斥量的副作用显会得过大。例如,如果你所做的只是计算成员函数被调用了多少次,使用`std::atomic` 修饰的counter(保证其他线程视这个操作为不可分割的发生,参见item40)通常会是一个开销更小的方法。(然而它是否轻量取决于你使用的硬件和标准库中互斥量的实现。)以下是如何使用`std::atomic`来统计调用次数。
|
||||
|
||||
```c++
|
||||
class Point { // 2D point
|
||||
@ -102,7 +102,7 @@ private:
|
||||
|
||||
与`std::mutex`一样,`std::atomic`是`move-only`类型,所以在`Point`中调用`Count`的意思就是`Point`也是`move-only`的。
|
||||
|
||||
因为对`std::atomic`变量的操作通常比互斥量的获取和释放的消耗更小,所以你可能更倾向与依赖`std::atomic`。例如,在一个类中,缓存一个开销昂贵的`int`,你就会尝试使用一对`std::atomic`变量而不是互斥锁。
|
||||
因为对`std::atomic`变量的操作通常比互斥量的获取和释放的消耗更小,所以你可能会过度倾向与依赖`std::atomic`。例如,在一个类中,缓存一个开销昂贵的`int`,你就会尝试使用一对`std::atomic`变量而不是互斥锁。
|
||||
|
||||
```c++
|
||||
class Widget {
|
||||
@ -126,7 +126,7 @@ private:
|
||||
};
|
||||
```
|
||||
|
||||
这是可行的,但有时运行会比它做到更加困难。考虑:
|
||||
这是可行的,但难以避免有时出现重复计算的情况。考虑:
|
||||
|
||||
+ 一个线程调用`Widget::magicValue`,将`cacheValid`视为`false`,执行这两个昂贵的计算,并将它们的和分配给`cachedValue`。
|
||||
+ 此时,第二个线程调用`Widget::magicValue`,也将`cacheValid`视为`false`,因此执行刚才完成的第一个线程相同的计算。(这里的“第二个线程”实际上可能是其他几个线程。)
|
||||
|
@ -25,7 +25,7 @@ public:
|
||||
|
||||
再进一步,如果一个类显式声明了拷贝操作,编译器就不会生成移动操作。这种限制的解释是如果声明拷贝操作就暗示着默认逐成员拷贝操作不适用于该类,编译器会明白如果默认拷贝不适用于该类,移动操作也可能是不适用的。
|
||||
|
||||
这是另一个方向。声明移动操作使得编译器不会生成拷贝操作。(编译器通过给这些函数加上delete来保证,参见Item11)。比较,如果逐成员移动对该类来说不合适,也没有理由指望逐成员考吧操作是合适的。听起来会破坏C++98的某些代码,因为C++11中拷贝操作可用的条件比C++98更受限,但事实并非如此。C++98的代码没有移动操作,因为C++98中没有移动对象这种概念。只有一种方法能让老代码使用用户声明的移动操作,那就是使用C++11标准然后添加这些操作, 并在享受这些操作带来的好处同时接受C++11特殊成员函数生成规则的限制。
|
||||
这是另一个方向。声明移动操作使得编译器不会生成拷贝操作。(编译器通过给这些函数加上delete来保证,参见Item11)。毕竟,如果逐成员移动对该类来说不合适,也没有理由指望逐成员拷贝操作是合适的。听起来会破坏C++98的某些代码,因为C++11中拷贝操作可用的条件比C++98更受限,但事实并非如此。C++98的代码没有移动操作,因为C++98中没有移动对象这种概念。只有一种方法能让老代码使用用户声明的移动操作,那就是使用C++11标准然后添加这些操作, 并在享受这些操作带来的好处同时接受C++11特殊成员函数生成规则的限制。
|
||||
|
||||
也许你早已听过_Rule of Three_规则。这个规则告诉我们如果你声明了拷贝构造函数,拷贝赋值运算符,或者析构函数三者之一,你应该也声明其余两个。它来源于长期的观察,即用户接管拷贝操作的需求几乎都是因为该类会做其他资源的管理,这也几乎意味着1)无论哪种资源管理如果能在一个拷贝操作内完成,也应该在另一个拷贝操作内完成2)类析构函数也需要参与资源的管理(通常是释放)。通常意义的资源管理指的是内存(如STL容器会动态管理内存),这也是为什么标准库里面那些管理内存的类都声明了“the big three”:拷贝构造,拷贝赋值和析构。
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
在C++11中存在四种智能指针:`std::auto_ptr`, `std::unique_ptr`, `std::shared_ptr`,` std::weak_ptr`。都是被设计用来帮助管理动态对象的生命周期,在适当的时间通过适当的方式来销毁对象,以避免出现资源泄露或者异常行为。
|
||||
|
||||
`std::auto_ptr`是C++98的遗留物,它是一次标准化的尝试,后来变成了C++11的`std::unique_ptr`。要正确的模拟原生制作需要移动语义,但是C++98没有这个东西。取而代之,`std::auto_ptr`拉拢拷贝操作来达到自己的移动意图。这导致了令人奇怪的代码(拷贝一个`std::auto_ptr`会将它本身设置为null!)和令人沮丧的使用限制(比如不能将`std::auto_ptr`放入容器)。
|
||||
`std::auto_ptr`是C++98的遗留物,它是一次标准化的尝试,后来变成了C++11的`std::unique_ptr`。要正确的模拟原生指针需要移动语义,但是C++98没有这个东西。取而代之,`std::auto_ptr`拉拢拷贝操作来达到自己的移动意图。这导致了令人奇怪的代码(拷贝一个`std::auto_ptr`会将它本身设置为null!)和令人沮丧的使用限制(比如不能将`std::auto_ptr`放入容器)。
|
||||
|
||||
`std::unique_ptr`能做`std::auto_ptr`可以做的所有事情以及更多。它能高效完成任务,而且不会扭曲拷贝语义。在所有方面它都比`std::unique_ptr`好。现在`std::auto_ptr`唯一合法的使用场景就是代码使用C++98编译器编译。除非你有上述限制,否则你就该把`std::auto_ptr`替换为`std::unique_ptr`而且绝不回头。
|
||||
|
||||
|
@ -86,5 +86,5 @@ std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
|
||||
|
||||
### 记住
|
||||
|
||||
- 像`std::shared_ptr`使用`std::weak_ptr`可能会悬空。
|
||||
- 用`std::weak_ptr`替代可能会悬空的`std::shared_ptr`。
|
||||
- `std::weak_ptr`的潜在使用场景包括:caching、observer lists、打破`std::shared_ptr`指向循环。
|
Loading…
Reference in New Issue
Block a user