Какие проблемы есть у умных указателей в C++?

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

Ответ

Умные указатели (unique_ptr, shared_ptr, weak_ptr) решают многие проблемы с ручным управлением памятью, но имеют свои нюансы:

  1. Циклические ссылки (только для shared_ptr). Если два объекта владеют друг другом через shared_ptr, счётчики ссылок никогда не обнулятся, что приводит к утечке памяти.

    struct Node {
        std::shared_ptr<Node> other;
    };
    auto a = std::make_shared<Node>();
    auto b = std::make_shared<Node>();
    a->other = b; // a владеет b
    b->other = a; // b владеет a -> ЦИКЛ
    // a и b не будут уничтожены после выхода из области видимости

    Решение: Разрывать циклы, заменяя одну из ссылок на weak_ptr.

  2. Накладные расходы. shared_ptr хранит два указателя (на объект и на блок управления с счётчиками) и выполняет атомарные операции для изменения счётчика ссылок, что дороже сырого указателя.

  3. Проблемы совместимости с массивами. shared_ptr<T> по умолчанию использует delete, а не delete[]. Для массивов нужно указывать кастомный делитер.

    // Правильно для массива
    std::shared_ptr<int[]> arr(new int[10], std::default_delete<int[]>());
    // Или, начиная с C++17:
    std::shared_ptr<int[]> arr(new int[10]);
    // unique_ptr поддерживает массивы "из коробки":
    std::unique_ptr<int[]> u_arr(new int[10]);
  4. Риск "прострела себе в ногу" при использовании get(). Передача сырого указателя, полученного через ptr.get(), в код, который может сохранить его или удалить, нарушает семантику владения и ведёт к UB или двойному удалению.

  5. Неявные преобразования. shared_ptr неявно преобразуется из совместимых типов, что иногда может скрывать ошибки. unique_ptr более строгий и требует явного перемещения (std::move).