C++ weak_ptr 是如何工作的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5671241/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 18:39:05  来源:igfitidea点击:

How does weak_ptr work?

c++boostweak-referencestr1weak-ptr

提问by Oliver Zheng

I understand how to use weak_ptrand shared_ptr. I understand how shared_ptrworks, by counting the number of references in its object. How does weak_ptrwork? I tried reading through the boost source code, and I'm not familiar enough with boost to understand all the things it uses.

我了解如何使用weak_ptrshared_ptrshared_ptr通过计算其对象中的引用数量,我了解了工作原理。如何weak_ptr工作?我尝试通读 boost 源代码,但我对 boost 不够熟悉,无法理解它使用的所有内容。

Thanks.

谢谢。

回答by Paul Groke

shared_ptruses an extra "counter" object (aka. "shared count" or "control block") to store the reference count. (BTW: that "counter" object also stores the deleter.)

shared_ptr使用额外的“计数器”对象(又名“共享计数”或“控制块”)来存储引用计数。(顺便说一句:那个“计数器”对象也存储了删除器。)

Every shared_ptrand weak_ptrcontains a pointer to the actual pointee, and a second pointer to the "counter" object.

每个shared_ptrweak_ptr包含一个指向实际被指点者的指针,以及一个指向“计数器”对象的第二个指针。

To implement weak_ptr, the "counter" object stores two different counters:

为了实现weak_ptr,“计数器”对象存储两个不同的计数器:

  • The "use count" is the number of shared_ptrinstances pointing to the object.
  • The "weak count" is the number of weak_ptrinstances pointing to the object, plus one if the "use count" is still > 0.
  • “使用计数”是shared_ptr指向对象的实例数。
  • “弱计数”是weak_ptr指向对象的实例数,如果“使用计数”仍然 > 0,则加一。

The pointee is deleted when the "use count" reaches zero.

当“使用计数”达到零时,指针被删除。

The "counter" helper object is deleted when the "weak count" reaches zero (which means the "use count" must also be zero, see above).

当“弱计数”达到零(这意味着“使用计数”也必须为零,见上文)时,“计数器”助手对象将被删除。

When you try to obtain a shared_ptrfrom a weak_ptr, the library atomically checks the "use count", and if it's > 0 increments it. If that succeeds you get your shared_ptr. If the "use count" was already zero you get an empty shared_ptrinstance instead.

当您尝试shared_ptr从 a获取 a时weak_ptr,库会自动检查“使用计数”,如果它 > 0,则增加它。如果成功,您将获得您的shared_ptr. 如果“使用计数”已经为零,则会得到一个空shared_ptr实例。



EDIT: Now, why do they add one to the weak count instead of just releasing the "counter" object when both counts drop to zero? Good question.

编辑:现在,当两个计数都降为零时,为什么他们在弱计数上加一,而不是仅仅释放“计数器”对象?好问题。

The alternative would be to delete the "counter" object when both the "use count" and the "weak count" drop to zero. Here's the first reason: Checking two (pointer sized) counters atomically is not possible on every platform, and even where it is, it's more complicated than checking just one counter.

另一种方法是当“使用计数”和“弱计数”都降为零时删除“计数器”对象。这是第一个原因:不可能在每个平台上原子地检查两个(指针大小的)计数器,即使在那里,它也比只检查一个计数器更复杂。

Another reason is that the deleter must stay valid until it has finished executing. Since the deleter is stored in the "counter" object, that means the "counter" object must stay valid. Consider what could happen if there is one shared_ptrand one weak_ptrto some object, and they are reset at the same time in concurrent threads. Let's say the shared_ptrcomes first. It decreases the "use count" to zero, and begins executing the deleter. Now the weak_ptrdecreases the "weak count" to zero, and finds the "use count" is zero as well. So it deletes the "counter" object, and with it the deleter. While the deleter is still running.

另一个原因是删除器必须保持有效直到它完成执行。由于删除器存储在“计数器”对象中,这意味着“计数器”对象必须保持有效。试想,如果有一个会发生什么shared_ptr,一个weak_ptr一些对象,它们在并发线程同时复位。假设shared_ptr先到先得。它将“使用计数”减少到零,并开始执行删除器。现在将weak_ptr“弱计数”减少到零,并发现“使用计数”也为零。所以它删除了“计数器”对象,并删除了删除器。虽然删除程序仍在运行。

Of course there would be different ways to assure that the "counter" object stays alive, but I think increasing the "weak count" by one is a very elegant and intuitive solution. The "weak count" becomes the reference count for the "counter" object. And since shared_ptrs reference the counter object too, they too have to increment the "weak count".

当然,会有不同的方法来确保“计数器”对象保持活动状态,但我认为将“弱计数”增加一个是一种非常优雅和直观的解决方案。“弱计数”成为“计数器”对象的引用计数。并且由于shared_ptrs 也引用了计数器对象,因此它们也必须增加“弱计数”。

A probably even more intuitive solution would be to increment the "weak count" for every single shared_ptr, since every single shared_ptrhold's a reference to the "counter" object.

一个可能更直观的解决方案是为每个单独增加“弱计数” shared_ptr,因为每个shared_ptr保持都是对“计数器”对象的引用。

Adding one for all shared_ptrinstances is just an optimization (saves one atomic increment/decrement when copying/assigning shared_ptrinstances).

为所有shared_ptr实例添加一个只是一种优化(在复制/分配shared_ptr实例时节省一个原子增量/减量)。

回答by user3726672

Basically, a "weak_ptr" is an ordinary "T*" pointer that lets you RECOVER a strong reference, i.e. "shared_ptr", later in the code.

基本上,“weak_ptr”是一个普通的“T*”指针,它可以让您在代码的后面恢复强引用,即“shared_ptr”。

Just like an ordinary T*, the weak_ptr doesn't do any reference-counting. Internally, to support reference-counting on an arbitrary type T, the STL (or any other library implementing this kind of logic) creates a wrapper object we'll call "Anchor". "Anchor" exists solely to implement the reference count and "when count is zero, call delete" behavior we need.

就像普通的 T* 一样,weak_ptr 不做任何引用计数。在内部,为了支持对任意类型 T 的引用计数,STL(或任何其他实现这种逻辑的库)创建了一个我们称为“Anchor”的包装对象。“锚点”的存在仅仅是为了实现我们需要的引用计数和“当计数为零时,调用删除”行为。

In a strong reference, the shared_ptr implements its copy, operator=, constructor, destructor, and other pertinent APIs to update "Anchor"'s reference count. This is how a shared_ptr ensures that your "T" lives for exactly as long as somebody is using it. In a "weak_ptr", those same APIs just copy the actual Anchor ptr around. They do NOT update reference counts.

在强引用中,shared_ptr 实现了它的 copy、operator=、构造函数、析构函数和其他相关的 API 来更新“Anchor”的引用计数。这就是 shared_ptr 如何确保您的“T”与有人使用它的时间完全相同。在“weak_ptr”中,那些相同的 API 只是复制实际的 Anchor ptr。他们不更新引用计数。

This is why the most important APIs of "weak_ptr" are "expired" and the poorly-named "lock". "Expired" tells you if the underlying object is still around- i.e. "Has it already deleted itself because all strong references went out of scope?". "Lock" will (if it can) convert the weak_ptr to a strong reference shared_ptr, restoring reference-counting.

这就是为什么“weak_ptr”最重要的 API 是“过期”和名称不佳的“锁”。“过期”告诉你底层对象是否仍然存在——即“它是否已经删除了自己,因为所有强引用都超出了范围?”。“锁定”将(如果可以)将weak_ptr 转换为强引用shared_ptr,恢复引用计数。

BTW, "lock" is a terrible name for that API. You aren't (just) invoking a mutex, you're creating a strong reference from a weak one, with that "Anchor" acting. The biggest flaw in both templates is that they did not implement operator->, so to do anything with your object you have to recover the raw "T*". They did this mostly to support things like "shared_ptr", because primitive types don't support the "->" operator.

顺便说一句,“锁定”对于该 API 来说是一个糟糕的名字。您不是(只是)调用互斥锁,而是从弱引用创建强引用,并使用“锚点”作用。两个模板的最大缺陷是它们没有实现 operator->,因此要对您的对象执行任何操作,您必须恢复原始的“T*”。他们这样做主要是为了支持“shared_ptr”之类的东西,因为原始类型不支持“->”运算符。