К какому моменту указатель raw становится недействительным после вызова drop? Рассмотрите код и назовите по...

К какому моменту указатель raw становится недействительным после вызова drop? Рассмотрите код и назовите последствие обращения к нему.

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

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

В этом коде raw становится недействительным внутри drop, когда p.reset() уничтожает объект int: p был его единственным владельцем. Разыменование raw после возврата из drop — неопределённое поведение, потому что это висячий указатель.

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

RAII и умные указатели появились как средство связать управление ресурсом со временем жизни объекта-владельца. В стандартной библиотеке C++ std::shared_ptr предназначен для случаев, когда несколько объектов должны совместно владеть одним ресурсом.

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

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

#include <iostream> #include <memory> void drop(std::shared_ptr<int>& p) { p.reset(); } int main() { auto owner = std::make_shared<int>(42); int* raw = owner.get(); drop(owner); std::cout << *raw << ' '; }

До вызова drop переменная owner владеет объектом, а raw лишь указывает на него и не продлевает его время жизни. После reset() владелец освобождает объект, поэтому raw сохраняет только адрес уже уничтоженного объекта.

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

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

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

raw не участвует в подсчёте владельцев. Методы get() и неявное получение сырого указателя дают только временный доступ к адресу, но не передают владение и не создают дополнительной защиты времени жизни.

Передача по std::shared_ptr<int>& отличается от передачи по значению или по const std::shared_ptr<int>&. Неконстантная ссылка позволяет функции изменить исходный shared_ptr, поэтому такой параметр должен быть осознанным сигналом: функция может сбросить или заменить владение.

Если функции нужен только доступ к объекту, обычно используют const std::shared_ptr<int>&, ссылку на сам объект (int& или const int&) либо сырой указатель как явно невладеющий параметр. Если функция должна получить отдельное владение, применяют передачу shared_ptr по значению; если владение должно перейти однозначно — чаще подходит unique_ptr.

Безопасный вариант — не сохранять сырой указатель после операции, способной уничтожить объект:

#include <iostream> #include <memory> void drop(std::shared_ptr<int>& p) { p.reset(); } int main() { auto owner = std::make_shared<int>(42); drop(owner); if (!owner) std::cout << "object destroyed "; }

Важно отличать уничтожение объекта от освобождения управляющего блока. Если существует другой shared_ptr, reset() у owner уничтожит не объект, а только владение owner; в исходном примере второго владельца нет.

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

В обработчике кэша функция получила std::shared_ptr<Resource>& и при ошибке вызвала reset(). Другой код ранее сохранил Resource* для быстрого доступа. После сброса кэша этот сырой указатель стал висячим, хотя проблема проявлялась лишь при редком сценарии ошибки.

Первый вариант — оставить сырой указатель. Он экономичен, но требует строгего протокола времени жизни и легко становится источником неопределённого поведения. Второй вариант — хранить копию shared_ptr в потребителе; это продлевает жизнь ресурса, но может задержать освобождение и скрыть границы владения.

Выбранное решение — передавать потребителю std::shared_ptr<const Resource> на время операции, а для кратковременного невладеющего доступа использовать ссылку с гарантией, что сброс кэша в этот период невозможен. Это явно разделяет владение и доступ, предотвращает висячий указатель и не создаёт долгоживущих владельцев без необходимости.

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

  1. Вопрос: Безопасно ли передать shared_ptr по значению вместо неконстантной ссылки, если функция всё равно вызывает reset() у своего параметра?

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

  2. Вопрос: Продлевает ли время жизни объекта копирование сырого указателя, полученного через get()?

    Ответ: Нет. Копируется только адрес, а не запись в управляющем блоке. Время жизни продлевает копирование самого std::shared_ptr, а не T*; поэтому сырой указатель нужно рассматривать как невладеющий.

  3. Вопрос: Что изменится, если перед вызовом drop(owner) создать auto backup = owner?

    Ответ: У объекта появятся два владельца. После drop(owner) счётчик владельцев уменьшится, но backup продолжит владеть объектом, поэтому raw ещё будет указывать на живой объект. Однако полагаться на такую косвенную гарантию не следует: если backup уничтожить или сбросить до использования raw, указатель снова станет недействительным.