В чем разница умного указателя и обычного в C++
Что проверяют
Понимаете ли вы владение ресурсом: кто отвечает за освобождение памяти и как это выражено в типе. В ML это всплывает на инференсе и в расширениях: libtorch, ONNX Runtime, TensorRT, биндинги через pybind11. Следом спрашивают, чем unique_ptr отличается от shared_ptr и сколько стоит подсчёт ссылок.
Как отвечать
Обычный указатель T* это просто адрес. По типу не видно, кто должен вызвать delete, поэтому возможны утечки, двойное освобождение и висячие указатели. Умный указатель это RAII-класс: память освобождается в деструкторе, в том числе при исключении.
std::unique_ptr<T>: единоличное владение. Копировать нельзя, только перемещать черезstd::move. С deleter по умолчанию весит как обычный указатель и не даёт накладных расходов. Создаётся черезstd::make_unique;std::shared_ptr<T>: совместное владение со счётчиком ссылок в управляющем блоке. Счётчик атомарный, каждое копирование стоит атомарной операции.std::make_sharedвыделяет объект и блок одной аллокацией;std::weak_ptr<T>: наблюдатель без владения, нужен для разрыва циклов, доступ черезlock();- свой deleter: для ресурсов не из
new, например буфер на GPU, который освобождаетсяcudaFree, или хендл сессии из C API.
Правило, которое стоит озвучить: владеющий указатель умный, а сырой T* или ссылка остаются для параметров без владения, например void infer(const Tensor& t). В pybind11 держатель по умолчанию std::unique_ptr, а std::shared_ptr указывают явно, когда объект живёт и в Python, и в C++.
Где спотыкаются
Говорят, что умные указатели «работают на подсчёте ссылок». Считает ссылки только shared_ptr, у unique_ptr счётчика нет.
Ставят shared_ptr везде по привычке. Владение размывается, атомарные операции в горячем цикле заметны, а циклические ссылки без weak_ptr дают утечку.
Создают два shared_ptr из одного сырого указателя или делают shared_ptr<T>(this). Получается два управляющих блока и двойное освобождение. Для второго случая есть std::enable_shared_from_this.