3

std::shared_ptr需要在包含引用计数的堆上分配一个控制块。我从http://ootips.org/yonat/4dev/smart-pointers.html学到了另一种方法,它将所有引用保存在一个双向链表中。它不需要额外的分配,也不需要计数器,但引用对象本身更大。

是否有基准或任何明确的理由表明一种实现优于其他实现?

4

1 回答 1

8

该标准在理论上允许使用链表,但由于复制 ashared_ptr必须是线程安全的,因此使用链表实现它会更加困难。该列表需要由互斥体保护(或者是一个无锁列表,这要困难得多),以便每次 ashared_ptr被复制或超出范围时,该列表都可以被多个线程安全地修改。

使用原子类型进行引用计数并使用原子操作进行引用计数更新要简单得多,而且通常效率更高。

编辑:要回答下面的评论,仅使用原子指针来实现链表是不够的。要从列表中添加或删除一个节点(对应于增加和减少use_count),您需要以原子方式更新两个指针,以在添加/删除节点之前和之后调整节点中的链接。 std::atomic<T*>允许您以原子方式更新单个指针,但如果您需要以原子方式更新两个此类对象,则无济于事。由于这两个指针位于不同的节点中,因此它们并不相邻,因此即使是四字 CAS 也无济于事。

替代方案是保护整个列表的互斥锁(显然不利于争用)或每个列表节点的互斥锁,您可以锁定任何更新中涉及的每个节点的互斥锁,这会使用更多内存并一次影响多达三个节点,即需要锁定三个互斥锁。如果您的数量use_count()小于或等于五个,则复制/销毁任何一个都shared_ptr与复制/销毁共享同一指针所有权的任何其他实例进行竞争。在大多数更新是针对彼此相距较远的非邻居节点的情况下,您可能会因为使用次数较多而减少争用,但在一般情况下可能不会。大量程序使用shared_ptr使用计数为个位数。即使使用计数很高并且任何节点上都没有争用,您仍然必须锁定三个互斥锁并创建/销毁一个列表节点(可能需要堆分配)并更新其相邻节点中的指针,因此原子增量/减量尽管存在原子整数的争用,但它更简单并且仍然可以更快。

上次我在委员会反射器上提到shared_ptr不需要使用参考计数并且可以使用列表,我得到了答复:

鉴于标准现在承认多线程,有人真的这样做吗?

和(参考线程安全要求):

对于引用链接的实现,要(有效地)完成这项工作要困难得多。我什至不确定这是否可能,尽管它可能是。

于 2013-01-13T15:16:14.007 回答