weak_ptr 是如何工作的?

发布于 2024-11-01 02:26:21 字数 198 浏览 0 评论 0原文

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

谢谢。

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

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

千里故人稀 2024-11-08 02:26:22

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

每个 shared_ptrweak_ptr 都包含一个指向实际指针对象的指针,以及另一个指向“计数器”对象的指针。

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

  • “使用计数”是指向该对象的shared_ptr实例的数量。
  • “弱计数”是指向该对象的 weak_ptr 实例的数量,如果“使用计数”仍然 > 则加一。 0.

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

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

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


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

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

另一个原因是删除器必须保持有效直到它执行完毕。由于删除器存储在“计数器”对象中,这意味着“计数器”对象必须保持有效。考虑一下如果某个对象有一个 shared_ptr 和一个 weak_ptr,并且它们在并发线程中同时重置,会发生什么情况。假设 shared_ptr 首先出现。它将“使用计数”减少到零,并开始执行删除器。现在,weak_ptr 将“弱计数”减少到零,并发现“使用计数”也为零。因此它删除了“计数器”对象以及删除器。当删除器仍在运行时。

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

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

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

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

Every shared_ptr and weak_ptr contains a pointer to the actual pointee, and a second pointer to the "counter" object.

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

  • The "use count" is the number of shared_ptr instances pointing to the object.
  • The "weak count" is the number of weak_ptr instances pointing to the object, plus one if the "use count" is still > 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_ptr from 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_ptr instance instead.


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_ptr and one weak_ptr to some object, and they are reset at the same time in concurrent threads. Let's say the shared_ptr comes first. It decreases the "use count" to zero, and begins executing the deleter. Now the weak_ptr decreases 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.

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".

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

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

离去的眼神 2024-11-08 02:26:22

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

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

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

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

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

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

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.

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.

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.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文