Подтвердить что ты не робот

Каков максимальный счетчик ссылок в std:: shared_ptr? Что произойдет, если вы попытаетесь его превзойти?

Если мы предположим, что std::shared_ptr хранит счетчик ссылок (который я понимаю, стандарт не требует, но я не знаю о каких-либо реализациях, которые этого не делают), этот счетчик ссылок имеет ограниченное количество бит, а это означает существует максимальное количество ссылок, которые поддерживаются. Это приводит к двум вопросам:

  • Что это за максимальное значение?
  • Что произойдет, если вы попытаетесь его превзойти (например, скопировав std:: shared_ptr, который ссылается на объект с максимальным количеством ссылок)? Обратите внимание, что конструктор std::shared_ptr copy объявлен noexcept.

Является ли стандартом пролить свет на любой из этих вопросов? Как насчет общих реализаций, например gcc, MSVC, Boost?

4b9b3361

Ответ 1

Мы можем получить некоторую информацию из функции shared_ptr::use_count(). В §20.7.2.2.5 говорится:

long use_count() const noexcept;

Возвращает: число shared_ptr объектов, *this включено, которые разделяют владение с помощью *this или 0, когда *this пуст.

[Примечание: use_count() не обязательно эффективно.-end note]

На первый взгляд тип возвращаемого типа long, кажется, отвечает на первый вопрос. Однако примечание, по-видимому, подразумевает, что shared_ptr может использовать любой тип подсчета ссылок, к которому он хочет, включая такие вещи, как список ссылок. Если бы это было так, тогда теоретически не было бы максимального количества ссылок (хотя, конечно, был бы практический предел).

Нет другой ссылки на ограничения на количество ссылок на один и тот же объект, который я мог найти.

Интересно отметить, что use_count документируется как не бросать, так и (очевидно) правильно сообщать счетчик; если в реализации не используется элемент long для подсчета, я не вижу, как обе они могут быть теоретически гарантированы в любое время.

Ответ 2

Я не уверен, что предлагает стандарт, но посмотрите на него практически:

Счетчик ссылок скорее всего является своего рода переменной std::size_t. Эта переменная может содержать значения до -1+2^32 в 32-битных средах и до -1+2^64 в 64-разрядных средах.

Теперь изображение, которое должно было бы произойти, чтобы эта переменная достигла этого значения: вам понадобились бы экземпляры 2 ^ 32 или 2 ^ 64 shared_ptr. Это много. На самом деле, так много, что вся память будет исчерпана задолго до того, как вы достигнете этого числа, поскольку один shared_ptr имеет размер около 8/16 байтов.

Таким образом, вы вряд ли сможете достичь предела ссылочного счета, если размер переменной refcount достаточно велик.

Ответ 3

Стандарт не говорит; как вы говорите, он даже не требует ссылки считая. С другой стороны, есть (или было) выражение в стандарт (или, по крайней мере, в стандарте C), который превышает выполнение пределов undefined. Так что почти наверняка официальный Ответ.

На практике я ожидаю, что большинство реализаций будут поддерживать подсчет как size_t или ptrdiff_t. На машинах с плоской адресацией это в значительной степени означает, что вы не можете создать достаточное количество ссылок, чтобы вызвать переполнение. (На таких машинах один объект может занимать все память и size_t или ptrdiff_t имеют тот же размер, что и указатель. Поскольку каждый указатель счетчика ссылок имеет отдельный адрес, никогда не будет больше, чем вписывается в указатель.) На машинах с сегментированным архитектуры, однако переполнение вполне возможно.

Как указывает Джон, стандарт также требует std::shared_ptr::use_count(), чтобы вернуть a long. Я не уверен, что рассуждение здесь: либо size_t, либо ptrdiff_t сделает больше смысл здесь. Но если реализация использует другой тип для количество ссылок, предположительно, правила преобразования в long будут apply: "значение не изменяется, если оно может быть представлено в тип назначения (и ширина битового поля); в противном случае это значение равно определяемый реализацией "(стандарт C делает это несколько яснее:" значение, определяемое реализацией" может быть сигналом.)

Ответ 4

Вы можете узнать, что произойдет, создав общие указатели, используя новое место размещения и никогда не удаляя их. Вы можете легко ударить по 32-разрядному пределу.

Ответ 5

Стандарт С++ 11 указывает long как возвращаемый тип функции наблюдателя use_count(), но явно не указывает, должна ли реализация поддерживать до 2^(sizeof(long)*8-1)-1 общих владельцев.

Он также не указывает, что происходит, когда счетчик ссылок переполняется.

Реализация boost::shared_ptr (например, 1.58 на Fedora 23, x86-64) внутренне использует счетчик 32 бит и не проверяет наличие переполнения.

Это означает:

  • максимальное количество ссылок 2^31-1.
  • если у вас есть возможность переполнения и освобождения, вы можете столкнуться с некоторыми проблемами послепродажного обслуживания.

Так как 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;
}

См. также:

Реализация shared_pointer GNU STL (libstdС++) основана на Boost 1.32, а также имеет эту проблему (на Fedora 23/x86-64) - там используется тип _Atomic_word для подсчета ссылок. Он также "только" 32 бит и не проверяется на переполнение.

В отличие от реализации LLVM libС++ shared_ptr используется long как ссылочный счетчик, то есть на платформах LP64, таких как x86-64, вы можете совместно использовать объект между владельцами 2^63-1.