mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2025-03-24 08:10:19 +08:00
Update item19.md
This commit is contained in:
parent
aeeef9c1f3
commit
d4e423f363
@ -3,13 +3,13 @@
|
||||
|
||||
使用带垃圾回收的语言的程序员指着C++程序员笑看他们如何防止资源泄露。“真是原始啊!”他们嘲笑着说:“你们没有从1960年的Lisp那里得到启发吗,机器应该自己管理资源的生命周期而不应该依赖人类。”C++程序员翻白眼:“你得到的启发就是只有内存算资源,而且资源释放都是非确定性的吗?我们更喜欢通用,可预料的销毁,谢谢你。”但我们的虚张声势可能底气不足。因为垃圾回收真的很方便,而且手动管理生命周期真的就像是使用石头小刀和兽皮制作RAM电路。为什么我们不能同时有两个完美的世界:一个自动工作的世界(像是垃圾回收),一个销毁可预测的世界(像是析构)?
|
||||
|
||||
C++11中的`std::shared_ptr`将两者组合了起来。一个通过`std::shared_ptr`访问的对象其生命周期由指向它的指针们共享所有权(*shared ownership*)。没有特定的`std::shared_ptr`拥有该对象。相反,所有指向它的`std::shared_ptr`都能相互合作确保在它不再使用的那个点进行析构。当最后一个指向某对象的`std::shared_ptr`不再指向那(比如因为`std::shared_ptr`被销毁或者指向另一个不同的对象),`std::shared_ptr`会销毁它所指向的对象。就垃圾回收来说,客户端不需要关心指向对象的生命周期,而对象的析构是确定性的。
|
||||
C++11中的`std::shared_ptr`将两者组合了起来。一个通过`std::shared_ptr`访问的对象其生命周期由指向它的有共享所有权(*shared ownership*)的指针们来管理。没有特定的`std::shared_ptr`拥有该对象。相反,所有指向它的`std::shared_ptr`都能相互合作确保在它不再使用的那个点进行析构。当最后一个指向某对象的`std::shared_ptr`不再指向那(比如因为`std::shared_ptr`被销毁或者指向另一个不同的对象),`std::shared_ptr`会销毁它所指向的对象。就垃圾回收来说,客户端不需要关心指向对象的生命周期,而对象的析构是确定性的。
|
||||
|
||||
`std::shared_ptr`通过引用计数(*reference count*)来确保它是否是最后一个指向某种资源的指针,引用计数关联资源并跟踪有多少`std::shared_ptr`指向该资源。`std::shared_ptr`构造函数递增引用计数值(注意是通常——原因参见下面),析构函数递减值,拷贝赋值运算符做前面这两个工作。(如果`sp1`和`sp2`是`std::shared_ptr`并且指向不同对象,赋值“`sp1 = sp2;`”会使`sp1`指向`sp2`指向的对象。直接效果就是`sp1`引用计数减一,`sp2`引用计数加一。)如果`std::shared_ptr`在计数值递减后发现引用计数值为零,没有其他`std::shared_ptr`指向该资源,它就会销毁资源。
|
||||
`std::shared_ptr`通过引用计数(*reference count*)来确保它是否是最后一个指向某种资源的指针,引用计数关联资源并跟踪有多少`std::shared_ptr`指向该资源。`std::shared_ptr`构造函数递增引用计数值(注意是**通常**——原因参见下面),析构函数递减值,拷贝赋值运算符做前面这两个工作。(如果`sp1`和`sp2`是`std::shared_ptr`并且指向不同对象,赋值“`sp1 = sp2;`”会使`sp1`指向`sp2`指向的对象。直接效果就是`sp1`引用计数减一,`sp2`引用计数加一。)如果`std::shared_ptr`在计数值递减后发现引用计数值为零,没有其他`std::shared_ptr`指向该资源,它就会销毁资源。
|
||||
|
||||
引用计数暗示着性能问题:
|
||||
+ **`std::shared_ptr`大小是原始指针的两倍**,因为它内部包含一个指向资源的原始指针,还包含一个资源的引用计数值的原始指针。(这种实现法并不是标准要求的,但是我(指原书作者Scott Meyers)熟悉的所有标准库都这样实现。)
|
||||
+ **引用计数的存储空间必须动态分配**。 概念上,引用计数与所指对象关联起来,但是实际上被指向的对象不知道这件事情(译注:不知道有一个关联到自己的计数值)。因此它们没有办法存放一个引用计数值。(一个好消息是任何对象——甚至是内置类型的——都可以由`std::shared_ptr`管理。)[Item21](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/4.SmartPointers/item21.md)会解释使用`std::make_shared`创建`std::shared_ptr`可以避免引用计数的动态分配,但是还存在一些`std::make_shared`不能使用的场景,这时候引用计数就会动态分配。
|
||||
+ **`std::shared_ptr`大小是原始指针的两倍**,因为它内部包含一个指向资源的原始指针,还包含一个指向资源的引用计数值的原始指针。(这种实现法并不是标准要求的,但是我(指原书作者Scott Meyers)熟悉的所有标准库都这样实现。)
|
||||
+ **引用计数的内存必须动态分配**。 概念上,引用计数与所指对象关联起来,但是实际上被指向的对象不知道这件事情(译注:不知道有一个关联到自己的计数值)。因此它们没有办法存放一个引用计数值。(一个好消息是任何对象——甚至是内置类型的——都可以由`std::shared_ptr`管理。)[Item21](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/4.SmartPointers/item21.md)会解释使用`std::make_shared`创建`std::shared_ptr`可以避免引用计数的动态分配,但是还存在一些`std::make_shared`不能使用的场景,这时候引用计数就会动态分配。
|
||||
+ **递增递减引用计数必须是原子性的**,因为多个reader、writer可能在不同的线程。比如,指向某种资源的`std::shared_ptr`可能在一个线程执行析构(于是递减指向的对象的引用计数),在另一个不同的线程,`std::shared_ptr`指向相同的对象,但是执行的却是拷贝操作(因此递增了同一个引用计数)。原子操作通常比非原子操作要慢,所以即使引用计数通常只有一个*word*大小,你也应该假定读写它们是存在开销的。
|
||||
|
||||
我写道`std::shared_ptr`构造函数只是“通常”递增指向对象的引用计数会不会让你有点好奇?创建一个指向对象的`std::shared_ptr`就产生了又一个指向那个对象的`std::shared_ptr`,为什么我没说**总是**增加引用计数值?
|
||||
@ -64,7 +64,7 @@ std::shared_ptr<Widget> spw1(pw, loggingDel); //为*pw创建控制块
|
||||
…
|
||||
std::shared_ptr<Widget> spw2(pw, loggingDel); //为*pw创建第二个控制块
|
||||
```
|
||||
创建原始指针`pw`指向动态分配的对象很糟糕,因为它完全背离了这章的建议:对于使用智能指针而不是原始指针。(如果你忘记了该建议的动机,请翻到[本章开头](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/4.SmartPointers/item18.md))。撇开那个不说,创建`pw`那一行代码虽然让人厌恶,但是至少不会造成未定义程序行为。
|
||||
创建原始指针`pw`指向动态分配的对象很糟糕,因为它完全背离了这章的建议:倾向于使用智能指针而不是原始指针。(如果你忘记了该建议的动机,请翻到[本章开头](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/4.SmartPointers/item18.md))。撇开那个不说,创建`pw`那一行代码虽然让人厌恶,但是至少不会造成未定义程序行为。
|
||||
|
||||
现在,传给`spw1`的构造函数一个原始指针,它会为指向的对象创建一个控制块(因此有个引用计数值)。这种情况下,指向的对象是`*pw`(即`pw`指向的对象)。就其本身而言没什么问题,但是将同样的原始指针传递给`spw2`的构造函数会再次为`*pw`创建一个控制块(所以也有个引用计数值)。因此`*pw`有两个引用计数值,每一个最后都会变成零,然后最终导致`*pw`销毁两次。第二个销毁会产生未定义行为。
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user