Даны два фрагмента на C++: один использует raw pointers, второй — smart pointers (unique_ptr/shared_ptr). Проанализируйте поведение при копировании/перемещении и возможные утечки или двойное освобождение.
Кратко — сравнение поведения при копировании/перемещении и возможные утечки/двойное освобождение. 1) Raw pointers (сырые указатели) - Копирование: при присваивании копируется адрес (шаловое/shallow copy); владение не отслеживается. - Перемещение: не существует автоматической семантики перемещения — перемещение фактически то же, что копирование адреса. - Утечки/двойное освобождение: - Если ни один не вызовет `delete` — утечка. - Если два независимых кода вызовут `delete` для одного и того же адреса — двойное освобождение (UB). - Пример проблемы: `T* p = new T; T* q = p; delete p; delete q;` → UB. - Вывод: ответственность за `delete` полностью на программисте; плохая устойчивость к исключениям. 2) std::unique_ptr
- Копирование: запрещено (нет copy-конструктора/оператора), попытка копирования — ошибка компиляции. - Перемещение: разрешено; операция перемещения передаёт владение, исходный указатель становится пустым (равен `nullptr`). - Формально: после `u2 = std::move(u1);` владение у `u2`, `u1` пуст. - Утечки/двойное освобождение: - Правильное использование исключает двойное удаление и автоматизирует освобождение при выходе из области видимости. - Можно получить двойное удаление, если вручную создать два `unique_ptr` из одного сырого указателя: `std::unique_ptr a(p); std::unique_ptr b(p);` → двойное удаление. - Подходит для единоличного владения; хорош для RAII и безопасности при исключениях. 3) std::shared_ptr
- Копирование: разрешено; при копировании увеличивается счётчик ссылок (use count). - Если был один shared_ptr и созданы ещё две копии, итоговый счётчик: use_count =3=3=3. - Перемещение: при перемещении счётчик не увеличивается, владение перемещается в новый объект. - Утечки/двойное освобождение: - Объект удаляется автоматически, когда счётчик обращений достигает нуля (последний `shared_ptr` уничтожается). - Двойного удаления не будет при корректной работе счётчика. - Опасность: циклические ссылки через `shared_ptr` (A → shared_ptr, B → shared_ptr) приводят к тому, что счётчики никогда не станут нулём → утечка. - Решение циклов: использовать `std::weak_ptr` для «слабых» ссылок, не увеличивающих счётчик. - Примечание: счётчик обычно атомарен — накладные расходы и потенциальные затраты в многопоточной среде. Резюме / рекомендации - Для единоличного владения используйте `std::unique_ptr` (перемещение для передачи владения). - Для совместного владения используйте `std::shared_ptr` и ломайте циклы через `std::weak_ptr`. - Никогда не создавайте несколько умных указателей из одного сырого указателя (это приведёт к двойному удалению). - Если используете сырые указатели, явно договоритесь о владении и освобождении; лучше избегать `new`/`delete` вручную в современном C++. - Проверки: счётчик `shared_ptr` можно посмотреть через `use_count()`; ожидаемое завершение удаления при достижении use_count =0=0=0.
1) Raw pointers (сырые указатели)
- Копирование: при присваивании копируется адрес (шаловое/shallow copy); владение не отслеживается.
- Перемещение: не существует автоматической семантики перемещения — перемещение фактически то же, что копирование адреса.
- Утечки/двойное освобождение:
- Если ни один не вызовет `delete` — утечка.
- Если два независимых кода вызовут `delete` для одного и того же адреса — двойное освобождение (UB).
- Пример проблемы: `T* p = new T; T* q = p; delete p; delete q;` → UB.
- Вывод: ответственность за `delete` полностью на программисте; плохая устойчивость к исключениям.
2) std::unique_ptr - Копирование: запрещено (нет copy-конструктора/оператора), попытка копирования — ошибка компиляции.
- Перемещение: разрешено; операция перемещения передаёт владение, исходный указатель становится пустым (равен `nullptr`).
- Формально: после `u2 = std::move(u1);` владение у `u2`, `u1` пуст.
- Утечки/двойное освобождение:
- Правильное использование исключает двойное удаление и автоматизирует освобождение при выходе из области видимости.
- Можно получить двойное удаление, если вручную создать два `unique_ptr` из одного сырого указателя: `std::unique_ptr a(p); std::unique_ptr b(p);` → двойное удаление.
- Подходит для единоличного владения; хорош для RAII и безопасности при исключениях.
3) std::shared_ptr - Копирование: разрешено; при копировании увеличивается счётчик ссылок (use count).
- Если был один shared_ptr и созданы ещё две копии, итоговый счётчик: use_count =3=3=3.
- Перемещение: при перемещении счётчик не увеличивается, владение перемещается в новый объект.
- Утечки/двойное освобождение:
- Объект удаляется автоматически, когда счётчик обращений достигает нуля (последний `shared_ptr` уничтожается).
- Двойного удаления не будет при корректной работе счётчика.
- Опасность: циклические ссылки через `shared_ptr` (A → shared_ptr, B → shared_ptr) приводят к тому, что счётчики никогда не станут нулём → утечка.
- Решение циклов: использовать `std::weak_ptr` для «слабых» ссылок, не увеличивающих счётчик.
- Примечание: счётчик обычно атомарен — накладные расходы и потенциальные затраты в многопоточной среде.
Резюме / рекомендации
- Для единоличного владения используйте `std::unique_ptr` (перемещение для передачи владения).
- Для совместного владения используйте `std::shared_ptr` и ломайте циклы через `std::weak_ptr`.
- Никогда не создавайте несколько умных указателей из одного сырого указателя (это приведёт к двойному удалению).
- Если используете сырые указатели, явно договоритесь о владении и освобождении; лучше избегать `new`/`delete` вручную в современном C++.
- Проверки: счётчик `shared_ptr` можно посмотреть через `use_count()`; ожидаемое завершение удаления при достижении use_count =0=0=0.