В чем разница между std::unique_ptr и std::shared_ptr в C++?

«В чем разница между std::unique_ptr и std::shared_ptr в C++?» — вопрос из категории Управление памятью, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

std::unique_ptr реализует исключительное владение (exclusive ownership). Ресурсом владеет ровно один unique_ptr в каждый момент времени. Копирование запрещено, перемещение — разрешено. Это легковесный указатель с нулевыми накладными расходами (по размеру и производительности сравним с сырым указателем).

std::shared_ptr реализует разделяемое владение (shared ownership) через подсчет ссылок. Ресурс уничтожается, когда удаляется последний shared_ptr, ссылающийся на него. Поддерживает копирование.

Сравнение:

Критерий std::unique_ptr<T> std::shared_ptr<T>
Владение Эксклюзивное Разделяемое
Накладные расходы Минимальные (указатель) Указатель + контрольный блок со счетчиком ссылок
Копирование Запрещено Разрешено (инкрементирует счетчик)
Перемещение Разрешено (передача владения) Разрешено

Примеры:

// unique_ptr: фабричная функция возвращает объект, которым должен владеть только вызывающий.
std::unique_ptr<MyClass> createResource() {
    return std::make_unique<MyClass>();
}
auto resource = createResource(); // Владение передано в resource
// auto resource2 = resource; // Ошибка компиляции: нельзя копировать
auto resource2 = std::move(resource); // OK: владение передано resource2, resource теперь nullptr

// shared_ptr: несколько компонентов системы используют один кэшированный ресурс.
auto cachedConfig = std::make_shared<Config>();
auto processor1 = cachedConfig; // Оба shared_ptr владеют одним объектом Config
auto processor2 = cachedConfig; // Счетчик ссылок = 3
processor1.reset(); // Счетчик ссылок = 2
// Объект Config будет уничтожен, когда выйдут из области видимости cachedConfig и processor2.

Важные замечания:

  • Циклические ссылки: shared_ptr могут создавать циклические зависимости, приводящие к утечкам памяти. Для их разрыва используется std::weak_ptr.
  • Правило: Всегда предпочитайте unique_ptr как более простой и эффективный инструмент. Используйте shared_ptr только тогда, когда семантика разделяемого владения действительно необходима.