Что происходит со счётчиком владельцев std::shared_ptr при передаче такого указателя в функцию по значению?
Если в функцию по значению передаётся обычный именованный std::shared_ptr, создаётся его копия: счётчик владельцев увеличивается на единицу. После завершения функции копия уничтожается, счётчик уменьшается, а управляемый объект удаляется только после исчезновения последнего владельца.
Для временного или явно перемещаемого std::shared_ptr обычно используется перемещение: новый параметр получает владение контрольным блоком, а исходный указатель становится пустым. Поэтому передача по значению может означать либо совместное владение через копирование, либо передачу существующего владения через перемещение.
std::shared_ptr появился как стандартный способ безопасно представлять разделяемое владение динамическим объектом. До RAII разработчику приходилось вручную согласовывать все места создания и удаления ресурса, что приводило к утечкам и преждевременному освобождению.
В отличие от std::unique_ptr, один объект std::shared_ptr допускает несколько владельцев. Для этого используется контрольный блок со счётчиком сильных владельцев и, при необходимости, счётчиком слабых ссылок.
Функция, принимающая std::shared_ptr по значению, получает собственную копию умного указателя. Это продлевает время жизни объекта на период выполнения функции, но требует операции копирования и изменения счётчика владельцев.
Неверное понимание этого механизма приводит к ошибочным выводам: можно преждевременно освободить объект, передав указатель по ссылке без гарантии времени жизни, либо создать ненужные копии и усложнить управление производительностью. В многопоточном коде операции с контрольным блоком также требуют синхронизации на уровне реализации, поэтому копирование не является бесплатным.
При передаче lvalue std::shared_ptr по значению параметр инициализируется копированием. Копия указывает на тот же объект и тот же контрольный блок, а счётчик сильных владельцев увеличивается на единицу.
Когда функция завершается, параметр уничтожается. Его счётчик уменьшается; если это был последний сильный владелец, вызывается деструктор управляемого объекта, а затем при отсутствии слабых ссылок освобождается контрольный блок.
В примере 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; для синхронного невладеющего доступа — ссылку или наблюдающий указатель. Такой выбор делает время жизни явным и предотвращает обращение к уничтоженному объекту.
Увеличивается ли счётчик при передаче временного std::shared_ptr по значению?
Не обязательно. Для временного объекта обычно выбирается конструктор перемещения, поэтому контрольный блок передаётся параметру без создания дополнительного владельца. Важно различать семантику копирования именованного указателя и перемещения временного.
Продлевает ли передача std::shared_ptr по константной ссылке время жизни объекта?
Нет, сама ссылка не создаёт нового владельца и не увеличивает счётчик. Объект остаётся жив, только пока существует хотя бы один владеющий std::shared_ptr в другом месте; если вызывающий код уничтожит последний такой указатель во время некорректно организованного конкурентного доступа, ссылка не станет механизмом продления времени жизни.
Безопасно ли считать use_count() и на его основе принимать решение?
В однопоточном диагностическом коде значение может быть информативным, но в многопоточном коде оно не даёт устойчивой гарантии: другой поток способен скопировать или уничтожить владельца сразу после проверки. Кроме того, количество владельцев не определяет автоматически, можно ли менять объект или безопасно выполнять конкретную операцию; для этого нужны подходящие правила синхронизации и корректный контракт владения.