Разберите следующий фрагмент на C++ с умными указателями: std::shared_ptr a(new A); std::shared_ptr b(new B); a->b = b; b->a = a; объясните, почему возможна утечка памяти и как её избежать
Кратко — почему утечка: - В приведённом коде оба объекта владеют друг другом через std::shared_ptr, получается циклическая ссылка (reference cycle). Счётчики ссылок никогда не падают до нуля, поэтому деструкторы не вызовутся и память не освободится. - Последовательность счётчиков: после создания aaa и bbb у каждого use_count=1\text{use\_count} = 1use_count=1. После `a->b = b` счётчик у bbb становится 222. После `b->a = a` счётчик у aaa становится 222. При выходе из области видимости оба счётчика уменьшатся, но останутся 111 и объекты не удалятся. Как избежать: 1) Использовать std::weak_ptr в одном направлении (обычный способ): - Сделать в одном из классов (обычно «владельцем» считается та сторона, чей существование логически держит другой) поле типа std::weak_ptr вместо std::shared_ptr. Пример: ```cpp struct B; struct A { std::shared_ptr b; }; struct B { std::weak_ptr a; }; int main() { auto a = std::make_shared(); auto b = std::make_shared(); a->b = b; b->a = a; // weak_ptr не увеличивает счётчик владения } // при выходе из main оба объекта корректно удалятся ``` 2) Альтернативы: - Явно разрывать цикл: перед выходом вызывать `a.reset()` или `b.reset()`. - Использовать «сырую» (raw) или наблюдающую ссылку для одной стороны, если владение и жизненный цикл управляются явно. - При доступе к объекту из weak_ptr нужно вызвать `lock()` и проверить результат. Дополнительно: используйте `std::make_shared` вместо `new` для эффективности; если нужен shared_from_this внутри объекта — наследуйте std::enable_shared_from_this.
- В приведённом коде оба объекта владеют друг другом через std::shared_ptr, получается циклическая ссылка (reference cycle). Счётчики ссылок никогда не падают до нуля, поэтому деструкторы не вызовутся и память не освободится.
- Последовательность счётчиков: после создания aaa и bbb у каждого use_count=1\text{use\_count} = 1use_count=1. После `a->b = b` счётчик у bbb становится 222. После `b->a = a` счётчик у aaa становится 222. При выходе из области видимости оба счётчика уменьшатся, но останутся 111 и объекты не удалятся.
Как избежать:
1) Использовать std::weak_ptr в одном направлении (обычный способ):
- Сделать в одном из классов (обычно «владельцем» считается та сторона, чей существование логически держит другой) поле типа std::weak_ptr вместо std::shared_ptr. Пример:
```cpp
struct B;
struct A { std::shared_ptr b; };
struct B { std::weak_ptr a; };
int main() {
auto a = std::make_shared();
auto b = std::make_shared();
a->b = b;
b->a = a; // weak_ptr не увеличивает счётчик владения
} // при выходе из main оба объекта корректно удалятся
```
2) Альтернативы:
- Явно разрывать цикл: перед выходом вызывать `a.reset()` или `b.reset()`.
- Использовать «сырую» (raw) или наблюдающую ссылку для одной стороны, если владение и жизненный цикл управляются явно.
- При доступе к объекту из weak_ptr нужно вызвать `lock()` и проверить результат.
Дополнительно: используйте `std::make_shared` вместо `new` для эффективности; если нужен shared_from_this внутри объекта — наследуйте std::enable_shared_from_this.