我了解如何使用weak_ptr
和shared_ptr
。我通过计算其对象中的引用数来理解shared_ptr
的工作原理。 weak_ptr
如何运作?我尝试阅读boost源代码,并且我不熟悉boost以了解它使用的所有内容。
感谢。
答案 0 :(得分:92)
shared_ptr
使用额外的“计数器”对象(又名“共享计数”或“控制块”)来存储引用计数。
(顺便说一下:“计数器”对象也存储删除器。)
每个shared_ptr
和weak_ptr
包含指向实际指针的指针,以及指向“计数器”对象的第二个指针。
要实现weak_ptr
,“counter”对象存储两个不同的计数器:
shared_ptr
个实例的数量。weak_ptr
个实例的数量,如果“使用次数”仍然是> 1,则为1。 0 当“使用次数”达到零时删除指针。
当“弱计数”达到零时,“计数器”辅助对象被删除(这意味着“使用计数”也必须为零,见上文)。
当您尝试从shared_ptr
获取weak_ptr
时,该库会自动检查“使用次数”,如果它是> 0递增它。如果成功,您将获得shared_ptr
。如果“使用次数”已经为零,则会得到一个空的shared_ptr
实例。
编辑:现在,为什么他们在弱计数中添加一个而不是仅在两个计数都降为零时释放“计数器”对象?好问题。
当“使用计数”和“弱计数”都降为零时,替代方法是删除“计数器”对象。这是第一个原因:在每个平台上都不可能原子地检查两个(指针大小)计数器,即使它在哪里,它也比只检查一个计数器更复杂。
另一个原因是删除器必须保持有效直到它完成执行。由于删除器存储在“计数器”对象中,这意味着“计数器”对象必须保持有效。考虑如果某个对象有一个shared_ptr
和一个weak_ptr
会发生什么,并且它们会在并发线程中同时重置。假设shared_ptr
首先出现。它将“使用次数”减少为零,并开始执行删除。现在weak_ptr
将“弱计数”减少到零,并且发现“使用计数”也为零。因此它删除了“计数器”对象,并删除了删除器。删除者仍然在运行。
当然会有不同的方法来确保“反”对象保持活力,但我认为将“弱计数”增加一个是一个非常优雅和直观的解决方案。 “弱计数”成为“计数器”对象的引用计数。由于shared_ptr
也引用了计数器对象,因此它们也必须增加“弱计数”。
一个可能更直观的解决方案是增加每个shared_ptr
的“弱计数”,因为每个shared_ptr
都拥有对“计数器”对象的引用。
为所有shared_ptr
个实例添加一个只是一个优化(在复制/分配shared_ptr
实例时保存一个原子增量/减量)。
答案 1 :(得分:-7)
基本上,“weak_ptr”是一个普通的“T *”指针,可让您在代码中稍后恢复强引用,即“shared_ptr”。
就像普通的T *一样,weak_ptr不会进行任何引用计数。在内部,为了支持对任意类型T的引用计数,STL(或实现这种逻辑的任何其他库)创建了一个我们称之为“Anchor”的包装器对象。 “锚点”仅用于实现引用计数和“当计数为零时,调用删除”行为我们需要。
在强引用中,shared_ptr实现其copy,operator =,constructor,析构函数和其他相关API以更新“Anchor”的引用计数。这就是shared_ptr如何确保你的“T”生存的时间与有人使用它一样长。在“weak_ptr”中,那些相同的API只是复制实际的Anchor ptr。它们不会更新引用计数。
这就是为什么“weak_ptr”最重要的API是“过期”而命名不佳的“锁定”。 “Expired”告诉您底层对象是否仍然存在 - 即“它是否已经自行删除,因为所有强引用都超出了范围?”。 “Lock”将(如果可以)将weak_ptr转换为强引用shared_ptr,恢复引用计数。
BTW,“锁定”是该API的可怕名称。你不是(只是)调用一个互斥锁,你正在创建一个弱引用的强引用,并使用“Anchor”。两个模板中最大的缺陷是它们没有实现operator->,所以要对你的对象做任何事情,你必须恢复原始的“T *”。他们这样做主要是为了支持“shared_ptr”之类的东西,因为原始类型不支持“ - >”操作