Функция возвращает указатель на объект, которым владеет локальный RAII-объект. Разберите последствия вызова get():
#include <iostream>
#include <memory>
int* get() {
auto value = std::make_unique<int>(42);
return value.get();
}
int main() {
int* p = get();
std::cout << *p << '
';
}
После выхода из get() объект std::unique_ptr<int> value уничтожается, а вместе с ним освобождается int. Указатель p становится висячим, поэтому выражение *p вызывает неопределённое поведение.
value.get() возвращает только невладеющий адрес; он не передаёт владение вызывающему коду. Для передачи владения функция должна вернуть std::unique_ptr<int>.
RAII возник как способ связать управление ресурсом с временем жизни объекта. Это решает проблему ручных парных операций вроде allocate/free или lock/unlock, когда ранний return, исключение или забытая ветка могут нарушить освобождение ресурса.
Владение оформляется объектом, а его деструктор автоматически освобождает ресурс при выходе объекта из области видимости. Указатель без информации о владельце такой гарантии не даёт.
В функции get() объект value владеет динамически выделенным int. Вызов value.get() лишь извлекает адрес ресурса, но не изменяет владельца и не продлевает время жизни объекта.
При возврате из функции локальный unique_ptr уничтожается. Он освобождает int, поэтому p указывает на уже завершившийся объект. Разыменование такого указателя — неопределённое поведение: программа может вывести старое значение, аварийно завершиться или повредить состояние процесса.
std::unique_ptr реализует единоличное владение. При уничтожении владельца вызывается удаление управляемого объекта; после этого все ранее полученные через get() адреса становятся недействительными.
Исправленный вариант передаёт владение явно:
Здесь объект unique_ptr перемещается или возвращается с использованием оптимизации возврата, а время жизни int продолжается до уничтожения p. Возвращать std::unique_ptr следует, когда вызывающая сторона должна стать владельцем ресурса.
Если функция только предоставляет временный доступ к объекту, можно вернуть ссылку или сырой указатель, но владелец обязан гарантировать, что объект живёт дольше этого доступа. Такой контракт слабее и требует внимательного контроля времени жизни.
Нельзя исправлять исходный код вызовом delete p: память уже освобождена владельцем, поэтому повторное освобождение также приводит к неопределённому поведению. std::shared_ptr нужен только при действительно совместном владении; замена им unique_ptr без необходимости добавляет счётчик ссылок и усложняет модель владения.
В модуле конфигурации функция возвращала const char* на строку, созданную внутри локального std::string. После возврата некоторые запросы читали освобождённую или уже недействительную память, причём ошибка проявлялась только при другой оптимизации сборки.
Рассматривались варианты:
std::string — безопасно передаёт результат, но может копировать или перемещать строку;std::unique_ptr<std::string> — явно передаёт владение, но избыточно для обычного значения;std::string_view — эффективно, однако требует гарантировать время жизни исходной строки;Выбрали возврат std::string, потому что функция вычисляла самостоятельное значение, а не предоставляла представление над долгоживущим хранилищем. Это устранило зависимость результата от локального объекта и сделало владение очевидным.
Можно ли вернуть value.get(), если перед этим вызвать value.release()?
Да, адрес после release() не становится автоматически недействительным: unique_ptr перестаёт владеть объектом и возвращает сырой указатель. Но ответственность за последующее освобождение полностью переходит вызывающему коду.
Например, результат можно принять в другой std::unique_ptr<int>:
На практике предпочтительнее сразу вернуть value, потому что это проще для чтения и безопаснее при сопровождении. После release() легко потерять ресурс или передать его неподходящему удалителю.
Продлевает ли std::move(value) время жизни объекта после выхода из функции?
Нет. std::move только превращает выражение в объект, из которого разрешено перемещение; сам по себе он не перемещает ресурс и не продлевает время жизни локального объекта.
При return std::move(value) перемещается владение в возвращаемый unique_ptr, если операция действительно выполнена. Но явный std::move при возврате локального объекта часто не нужен и может помешать оптимизации возврата; корректный обычный вариант — return value;.
Когда сырой указатель из unique_ptr::get() допустим?
Он допустим как временное невладеющее представление, например при передаче в функцию, которая не сохраняет указатель и не удаляет объект. Владелец должен оставаться живым, а сам unique_ptr не должен за это время перемещаться, сбрасываться или уничтожаться.
Такой указатель нельзя возвращать наружу без явного контракта времени жизни, сохранять дольше владельца или передавать функции, которая ожидает владение. Если владение нужно передать, используют перемещение unique_ptr, а если оно должно быть разделено — создают shared_ptr и передают его копию.