Что происходит со счётчиком владельцев std::shared ptr при передаче такого указателя в функцию по значению?

Что происходит со счётчиком владельцев std::shared_ptr при передаче такого указателя в функцию по значению?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Если в функцию по значению передаётся обычный именованный std::shared_ptr, создаётся его копия: счётчик владельцев увеличивается на единицу. После завершения функции копия уничтожается, счётчик уменьшается, а управляемый объект удаляется только после исчезновения последнего владельца.

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

Исторический контекст

std::shared_ptr появился как стандартный способ безопасно представлять разделяемое владение динамическим объектом. До RAII разработчику приходилось вручную согласовывать все места создания и удаления ресурса, что приводило к утечкам и преждевременному освобождению.

В отличие от std::unique_ptr, один объект std::shared_ptr допускает несколько владельцев. Для этого используется контрольный блок со счётчиком сильных владельцев и, при необходимости, счётчиком слабых ссылок.

Постановка проблемы

Функция, принимающая std::shared_ptr по значению, получает собственную копию умного указателя. Это продлевает время жизни объекта на период выполнения функции, но требует операции копирования и изменения счётчика владельцев.

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

Подробное решение

При передаче lvalue std::shared_ptr по значению параметр инициализируется копированием. Копия указывает на тот же объект и тот же контрольный блок, а счётчик сильных владельцев увеличивается на единицу.

Когда функция завершается, параметр уничтожается. Его счётчик уменьшается; если это был последний сильный владелец, вызывается деструктор управляемого объекта, а затем при отсутствии слабых ссылок освобождается контрольный блок.

#include <iostream> #include <memory> void inspect(std::shared_ptr<int> value) { std::cout << value.use_count() << ' '; } int main() { auto owner = std::make_shared<int>(42); inspect(owner); // внутри функции обычно 2 std::cout << owner.use_count(); // после функции снова 1 }

В примере owner и value совместно владеют одним объектом. Сам объект не копируется: копируется только std::shared_ptr, то есть ссылка на объект и контрольный блок.

Если передать временный объект или применить std::move, параметр обычно инициализируется перемещением. Счётчик владельцев при этом не обязан увеличиваться: параметр забирает внутреннее состояние исходного указателя, а исходный std::shared_ptr остаётся корректным, но обычно пустым.

Передачу по значению выбирают, когда функция должна получить собственную копию владельца, например сохранить её в поле или передать дальше. Если функция только кратковременно использует объект и не должна продлевать его жизнь, чаще подходит ссылка или сырой невладеющий указатель; это следует явно отражать в контракте.

Метод use_count() полезен для диагностики, но не должен служить основой логики многопоточной программы: значение может измениться сразу после проверки. Кроме того, совместное владение не устраняет циклы: взаимные std::shared_ptr по-прежнему могут препятствовать уничтожению объектов.

Ситуация из практики

Сервис запускает фоновую операцию, которой нужен объект конфигурации. Один вариант — передать конфигурацию по сырому указателю. Это не создаёт накладных расходов на счётчик, но фоновая операция может обратиться к уже уничтоженному объекту, если вызывающий код завершит его жизнь раньше.

Второй вариант — передать std::shared_ptr по значению. Копирование создаёт дополнительного владельца и гарантирует, что конфигурация останется живой до завершения операции. Минусами являются стоимость копирования контрольного блока и риск неявно продлить жизнь крупного графа объектов.

Третий вариант — передать std::unique_ptr через перемещение. Он обеспечивает ясное единоличное владение, но лишает вызывающий код возможности использовать объект после передачи и не подходит, если владение действительно разделяется.

Практическое решение зависит от контракта: для независимой фоновой работы с разделяемым временем жизни выбирают копию std::shared_ptr; для строго единственного владельца — перемещение std::unique_ptr; для синхронного невладеющего доступа — ссылку или наблюдающий указатель. Такой выбор делает время жизни явным и предотвращает обращение к уничтоженному объекту.

Что кандидаты часто упускают

  1. Увеличивается ли счётчик при передаче временного std::shared_ptr по значению?

    Не обязательно. Для временного объекта обычно выбирается конструктор перемещения, поэтому контрольный блок передаётся параметру без создания дополнительного владельца. Важно различать семантику копирования именованного указателя и перемещения временного.

  2. Продлевает ли передача std::shared_ptr по константной ссылке время жизни объекта?

    Нет, сама ссылка не создаёт нового владельца и не увеличивает счётчик. Объект остаётся жив, только пока существует хотя бы один владеющий std::shared_ptr в другом месте; если вызывающий код уничтожит последний такой указатель во время некорректно организованного конкурентного доступа, ссылка не станет механизмом продления времени жизни.

  3. Безопасно ли считать use_count() и на его основе принимать решение?

    В однопоточном диагностическом коде значение может быть информативным, но в многопоточном коде оно не даёт устойчивой гарантии: другой поток способен скопировать или уничтожить владельца сразу после проверки. Кроме того, количество владельцев не определяет автоматически, можно ли менять объект или безопасно выполнять конкретную операцию; для этого нужны подходящие правила синхронизации и корректный контракт владения.