22

如果我们假设std::shared_ptr存储引用计数(我意识到标准不需要,但我不知道有任何实现不需要),那么引用计数的位数是有限的,这意味着存在最大数量支持的引用。这就引出了两个问题:

  • 这个最大值是多少?
  • 如果您尝试超过它(例如,通过复制一个引用具有最大引用计数的对象的 std::shared_ptr )会发生什么?请注意,声明了std::shared_ptr的复制构造函数noexcept

该标准是否阐明了这些问题中的任何一个?常见的实现如何,例如 gcc、MSVC、Boost?

4

5 回答 5

20

我们可以从shared_ptr::use_count()函数中得到一些信息。§20.7.2.2.5 说:

long use_count() const noexcept;

返回:包含的、与 共享所有权的shared_ptr对象的数量,或者何时为空。*this*this0*this

[注:use_count()不一定有效。-结束注]

乍一看,long返回类型似乎回答了第一个问题。但是,该注释似乎暗示shared_ptr可以随意使用任何类型的引用计数,包括引用列表之类的东西。如果是这种情况,那么理论上不会有最大引用计数(尽管肯定会有实际限制)。

对于我能找到的同一对象的引用数量限制,没有其他参考。

有趣的是,use_count它记录了不抛出和(显然)正确报告计数;除非实现确实使用long成员进行计数,否则我看不出如何在理论上始终保证这两者。

于 2012-09-13T08:20:50.910 回答
7

我不确定标准的建议是什么,但实际看一下:

引用计数很可能是某种std::size_t变量。-1+2^32此变量在 32 位环境和-1+2^6464 位环境中最多可以保存值。

现在想象一下这个变量要达到这个值会发生什么:你需要 2^32 或 2^64 个shared_ptr实例。好多啊。事实上,这太多了,以至于在你达到这个数字之前很久就会耗尽所有内存,因为一个内存shared_ptr大约有 8/16 字节大。

因此,如果 refcount 变量的大小足够大,您不太可能达到引用计数的限制。

于 2012-09-13T08:24:08.297 回答
7

标准没有说;正如你所说,它甚至不需要引用计数。另一方面,标准中(或至少在 C 标准中)有(或曾经)声明超过实现限制是未定义的行为。所以这几乎可以肯定是官方的答案。

在实践中,我希望大多数实现将计数保持为 asize_t或 a ptrdiff_t。在具有平面寻址的机器上,这几乎意味着您无法创建足够的引用来导致溢出。(在这样的机器上,单个对象可以占用所有内存,size_t或者ptrdiff_t与指针具有相同的大小。由于每个引用计数指针都有一个不同的地址,所以永远不会超过一个指针的容量。)在机器上然而,对于分段架构,溢出是完全可以想象的。

正如乔恩指出的那样,该标准还要求 std::shared_ptr::use_count()返回一个long. 我不确定这里的理由是什么:或者在size_t这里ptrdiff_t更有意义。但是如果实现对引用计数使用不同的类型,大概转换规则long将适用:“如果它可以在目标类型(和位域宽度)中表示,则该值不变;否则,该值是实现定义。” (C 标准更清楚地说明了这一点:“实现定义的值”可以是一个信号。)

于 2012-09-13T09:04:42.003 回答
2

您可以通过使用placement new 实例化共享指针并且从不删除它们来了解会发生什么。然后,您可以轻松达到 32 位限制。

于 2012-09-13T21:37:21.517 回答
0

C++11 标准指定longuse_count()观察者函数的返回类型,但没有明确指定实现是否必须支持2^(sizeof(long)*8-1)-1共享所有权。

它也没有指定引用计数器溢出时会发生什么。

boost::shared_ptr实现(例如 Fedora 23、x86-64 上的 1.58)在内部使用 32 位计数器并且不检查溢出。

这意味着:

  1. 最大引用计数为2^31-1.
  2. 如果您有溢出和释放所有权,您最终可能会遇到一些释放后使用的问题

由于 boost 对不同平台使用不同的低级专业化,您可以通过设置断点来验证详细信息*add_ref_lock- 在 Fedora 23/x86-64 上,您将在此处停止:

/usr/include/boost/smart_ptr/detail/sp_counted_base_gcc_x86.hpp
[..]
int use_count_;        // #shared
int weak_count_;       // #weak + (#shared != 0)
[..]
bool add_ref_lock() // true on success
{
    return atomic_conditional_increment( &use_count_ ) != 0;
}

也可以看看:

GNU STL (libstdc++) shared_pointer 实现基于 Boost 1.32 并且也有这个问题(在 Fedora 23/x86-64 上) - 该_Atomic_word类型用于引用计数。它也是“仅”32 位的,不检查溢出。

相比之下,LLVM libc++shared_ptr实现使用 along作为引用计数器,即在 x86-64 等 LP64 平台上,您可以在最多2^63-1所有者之间共享一个对象。

于 2016-09-18T10:27:39.450 回答