Что такое умные указатели в C++, чем отличаются unique_ptr, shared_ptr и weak_ptr, как они реализованы и когда использовать
Что проверяют
Владеете ли вы идиомой владения ресурсом через время жизни объекта. Ждут не перечисления трёх классов, а выбора: какой указатель в каком случае и почему, плюс понимание, что внутри и сколько это стоит.
Как отвечать
unique_ptr это единоличное владение: копирования нет, есть перемещение, размер как у обычного указателя при пустом удалителе, а сам удалитель входит в тип. По умолчанию берут именно его.
shared_ptr это разделяемое владение через управляющий блок со счётчиком сильных и слабых ссылок. Счётчики атомарные, поэтому копирование не бесплатно, зато объект живёт, пока жив хоть один владелец. make_shared размещает объект и управляющий блок одним куском памяти.
weak_ptr не владеет, а наблюдает: доступ только через lock(), который вернёт либо рабочий shared_ptr, либо пустой.
auto node = std::make_shared<Node>();
std::weak_ptr<Node> parent = node;
if (auto p = parent.lock()) { use(*p); }Когда что: по умолчанию unique_ptr; shared_ptr только если владение действительно разделено и неизвестно, кто освободит последним; weak_ptr для разрыва циклов. Не владеющий параметр это ссылка или сырой указатель, а ресурс чужого C-интерфейса заворачивается в unique_ptr со своим удалителем.
Где спотыкаются
Делают shared_ptr вариантом по умолчанию. Атомарные счётчики в горячем коде стоят денег, а главное, владение размазывается и время жизни перестаёт быть предсказуемым. Функции, которой владение не нужно, передаётся ссылка, а не копия умного указателя.
Строят два независимых умных указателя от одного сырого. Получаются два управляющих блока и двойное освобождение; тот же эффект даёт попытка обернуть this, для чего существует отдельный базовый класс. Создание через make_unique и make_shared заодно снимает утечку при исключении в соседнем аргументе.
Не замечают цикла: два объекта ссылаются друг на друга сильными ссылками, счётчики не доходят до нуля, деструкторы не вызываются. Обратная ссылка делается слабой. Рядом вторая ошибка: проверка на просроченность отдельным вызовом и обращение следующей строкой, хотя между ними объект успевает умереть; безопасный способ один это lock().