Вызов release() у std::unique_ptr: кто отвечает за ресурс, если возвращённый указатель потерян?
release() отказывается от владения ресурсом, но не освобождает его. std::unique_ptr становится пустым, а ответственность за удаление переходит к вызывающему коду через возвращённый сырой указатель. Если этот указатель потерян, ресурс обычно будет навсегда утрачен, то есть произойдёт утечка памяти.
std::unique_ptr появился как безопасная замена ручному управлению ресурсами через пары new и delete. Его обычная модель — единственный владелец автоматически освобождает ресурс в деструкторе, что реализует принцип RAII.
Однако некоторые интерфейсы требуют явно передать владение в виде сырого указателя: например, C-совместимый API или функция, которая принимает объект и документирует обязанность вызывающего освободить его. Для таких случаев release() предоставляет осознанный выход из автоматического управления.
До вызова release() ресурс принадлежит std::unique_ptr, поэтому он будет освобождён при уничтожении владельца или при вызове reset(). После release() умный указатель больше не знает адрес ресурса и не вызовет свой удалитель.
Главный риск — ошибочно воспринимать release() как операцию освобождения. Это не так: функция только передаёт владение наружу. Потеря возвращённого адреса, исключение до передачи его новому владельцу или отсутствие последующего delete приводит к утечке.
release() возвращает хранимый сырой указатель и устанавливает внутренний указатель std::unique_ptr в nullptr. Деструктор std::unique_ptr после этого ничего не освобождает, потому что указатель больше не владеет ресурсом.
После release() новый владелец должен использовать совместимый способ освобождения. Для объекта, созданного как new T, это обычно delete; для массива, управляемого std::unique_ptr<T[]>, — delete[]; для внешнего ресурса — соответствующая функция библиотеки.
Если нужно просто заменить или уничтожить ресурс, обычно следует использовать reset(), а не release(). reset() сначала освобождает прежний ресурс через удалитель, а затем устанавливает новый указатель, поэтому он сохраняет автоматическое управление.
release() оправдан только при явной передаче владения API, которое принимает такую ответственность. Если функция лишь временно использует объект и не становится его владельцем, передавать ей результат release() нельзя: следует передать owner.get() или ссылку, сохранив владельца живым на всё время использования.
Модуль на C++ создаёт ресурс через std::unique_ptr, а старый C-интерфейс принимает владение через сырой указатель и освобождает его собственной функцией. Вариант с get() безопасен только тогда, когда C-интерфейс не забирает владение: после уничтожения std::unique_ptr указатель станет недействительным.
Вариант с release() корректен, если C-интерфейс действительно принимает владение и гарантированно вызовет нужный удалитель. Его минус — безопасность зависит от внешнего контракта: компилятор уже не контролирует, что ресурс будет освобождён.
Лучшее решение — адаптер с RAII, который хранит ресурс в std::unique_ptr с пользовательским удалителем и вызывает C-интерфейс только в специально определённой точке передачи. Если API можно изменить, предпочтительнее передавать std::unique_ptr по значению: передача владения станет явной и останется проверяемой средствами языка.
Освобождает ли release() ресурс, если std::unique_ptr после вызова уничтожается?
Нет. После release() умный указатель пуст, поэтому его деструктор не вызывает удалитель. Освобождение должен выполнить код, получивший возвращённый сырой указатель; если указатель утрачен, ресурс не будет освобождён.
Чем release() отличается от reset() без аргументов?
release() прекращает владение без вызова удалителя и возвращает адрес ресурса. reset() без аргументов прекращает владение с вызовом удалителя, после чего указатель также становится пустым. Поэтому release() предназначен для передачи владения, а reset() — для уничтожения ресурса.
Можно ли передать результат release() в обычный delete, если у unique_ptr был пользовательский удалитель?
Не всегда. Пользовательский удалитель мог вызывать не delete, а специальную функцию, например close или API-библиотеки. После release() информация о корректном способе освобождения не сопровождает сырой указатель, поэтому вызывающий код обязан знать и применить тот же удалитель; иначе возможны утечка, двойное освобождение или неопределённое поведение.