对象的引用计数通常存储在哪里?
Where is the reference count of an object typically stored?
如果我们有一些智能指针 class 可以接受任意对象并提供引用计数指针,我们如何实际存储引用计数的整数?引用计数必须在指向同一对象的智能指针 class 的所有实例之间共享。
我想到的一个解决方案是将引用计数存储在我们指向的对象中,但这对于通用解决方案来说并不是很好,因为每个对象都必须自己提供引用计数或继承来自提供它的某个对象。
std::shared_ptr
使用的一般解决方案是分配一个单独的控制块,其中包含引用计数(以及其他内容,例如:析构函数,weak_ptr 计数)。
(如果使用std::make_shared
,控制块和对象可以在同一个分配中)
你在第二段中描述的东西也存在,例如:它在Boost中被称为intrusive_ptr。
它是 "typically stored" 在对象设计所需的任何位置。侵入式智能指针需要与它们一起使用的 T
来为引用计数提供存储空间。这就是让他们 "intrusive";他们侵入了物体。
您概述的设计指定 "can take an arbitrary object." 因此,侵入式设计不在 table。
由于智能指针的许多实例必须访问同一个引用计数对象,因此该引用计数必须独立于任何一个实例。并且由于它还必须独立于 T
,因此它必须是一个对象,其生命周期独立于 T
和任何引用它的智能指针实例。
因此,智能指针在声明 T
的所有权后,还必须创建引用计数对象来管理它。通常,这是通过堆分配此类对象来完成的。智能指针的副本也得到一个指向引用计数的指针。
这也是为什么两个不同的 std::shared_ptr
构造函数声明对同一 T*
的所有权是非法的。您可以从已经拥有 T*
的 shared_ptr
进行复制,但不能直接将 T*
本身直接传递给构造函数。因为 T
无法访问引用计数,shared_ptr
的构造函数不知道其他人拥有它,所以它会创建第二个引用计数块。
有很多存储引用计数的策略,具体取决于您希望支持的操作。
这里的其他答案概述了一个选项,即在托管内存旁边分配一个辅助控制块,并让所有智能指针指向该辅助块。这使得快速确定准确的引用计数变得容易,但需要为每个智能指针进行额外分配,这可能会减慢速度,并且如果内存不足可能会失败。 (当控制块未与对象本身连续存储时,查找控制块的缓存友好性也存在问题)。
另一种选择是引用链接,其中没有明确的引用计数。相反,您通过所有智能指针循环地穿入一个 doubly-linked 列表。当向对象添加一个新的智能指针时,将该智能指针拼接到链表中,而当删除一个智能指针时,将该智能指针拼接出链表。这消除了对辅助分配的需要(除非,比如说,你需要一个自定义删除器)并改进了局部性,但使确定确切的引用计数变得昂贵。实际上需要精确的引用计数并不常见,因此这种权衡通常是合理的。
如果我们有一些智能指针 class 可以接受任意对象并提供引用计数指针,我们如何实际存储引用计数的整数?引用计数必须在指向同一对象的智能指针 class 的所有实例之间共享。
我想到的一个解决方案是将引用计数存储在我们指向的对象中,但这对于通用解决方案来说并不是很好,因为每个对象都必须自己提供引用计数或继承来自提供它的某个对象。
std::shared_ptr
使用的一般解决方案是分配一个单独的控制块,其中包含引用计数(以及其他内容,例如:析构函数,weak_ptr 计数)。
(如果使用std::make_shared
,控制块和对象可以在同一个分配中)
你在第二段中描述的东西也存在,例如:它在Boost中被称为intrusive_ptr。
它是 "typically stored" 在对象设计所需的任何位置。侵入式智能指针需要与它们一起使用的 T
来为引用计数提供存储空间。这就是让他们 "intrusive";他们侵入了物体。
您概述的设计指定 "can take an arbitrary object." 因此,侵入式设计不在 table。
由于智能指针的许多实例必须访问同一个引用计数对象,因此该引用计数必须独立于任何一个实例。并且由于它还必须独立于 T
,因此它必须是一个对象,其生命周期独立于 T
和任何引用它的智能指针实例。
因此,智能指针在声明 T
的所有权后,还必须创建引用计数对象来管理它。通常,这是通过堆分配此类对象来完成的。智能指针的副本也得到一个指向引用计数的指针。
这也是为什么两个不同的 std::shared_ptr
构造函数声明对同一 T*
的所有权是非法的。您可以从已经拥有 T*
的 shared_ptr
进行复制,但不能直接将 T*
本身直接传递给构造函数。因为 T
无法访问引用计数,shared_ptr
的构造函数不知道其他人拥有它,所以它会创建第二个引用计数块。
有很多存储引用计数的策略,具体取决于您希望支持的操作。
这里的其他答案概述了一个选项,即在托管内存旁边分配一个辅助控制块,并让所有智能指针指向该辅助块。这使得快速确定准确的引用计数变得容易,但需要为每个智能指针进行额外分配,这可能会减慢速度,并且如果内存不足可能会失败。 (当控制块未与对象本身连续存储时,查找控制块的缓存友好性也存在问题)。
另一种选择是引用链接,其中没有明确的引用计数。相反,您通过所有智能指针循环地穿入一个 doubly-linked 列表。当向对象添加一个新的智能指针时,将该智能指针拼接到链表中,而当删除一个智能指针时,将该智能指针拼接出链表。这消除了对辅助分配的需要(除非,比如说,你需要一个自定义删除器)并改进了局部性,但使确定确切的引用计数变得昂贵。实际上需要精确的引用计数并不常见,因此这种权衡通常是合理的。