♻️ Идеальное преступление: Как создать утечку памяти с помощью умных указателей?
С появлением
Самая частая причина «фантомных» утечек памяти в современном C++ это Циклическая зависимость (Circular Dependency).
🪤 Ловушка: Змея, кусающая себя за хвост
Представьте игру. У нас есть Игрок (
• Игрок должен знать, в какой Гильдии он состоит.
• Гильдия должна знать, кто её лидер (Игрок).
Вы пишете такой код:
Итог: Функция завершилась. Объекты больше никому в программе не нужны. Но их деструкторы никогда не вызовутся. Они держат друг друга в заложниках, потому что счетчик не упал до нуля. Вы потеряли память.
⚔️ Спаситель:
Чтобы разорвать цикл, мы должны определить, кто кем владеет (кто важнее), а кто просто ссылается. Допустим, Гильдия существует независимо от лидера, поэтому она будет просто "наблюдать" за ним.
✅ Правильный код:
Теперь при выходе из функции счетчик
👀 Как пользоваться
Так как
💡 Золотое архитектурное правило:
Стройте связи в виде дерева.
Сверху вниз - владение (
Снизу вверх - наблюдение (
#cpp #memory #smartpointers #leaks #oop #coding #tips
➡️ @cpp_geek
С появлением
std::shared_ptr в C++ многие выдохнули: "Наконец-то счетчик ссылок всё сделает за нас, больше никаких утечек!". Но умные указатели не обладают интеллектом. И их очень легко обмануть.Самая частая причина «фантомных» утечек памяти в современном C++ это Циклическая зависимость (Circular Dependency).
🪤 Ловушка: Змея, кусающая себя за хвост
Представьте игру. У нас есть Игрок (
Player) и Гильдия (Guild). • Игрок должен знать, в какой Гильдии он состоит.
• Гильдия должна знать, кто её лидер (Игрок).
Вы пишете такой код:
struct Player; // Предварительное объявление
struct Guild {
std::shared_ptr<Player> leader;
~Guild() { std::cout << "Guild deleted\n"; }
};
struct Player {
std::shared_ptr<Guild> myGuild;
~Player() { std::cout << "Player deleted\n"; }
};
void Play() {
auto p = std::make_shared<Player>(); // ref_count(Player) = 1
auto g = std::make_shared<Guild>(); // ref_count(Guild) = 1
p->myGuild = g; // ref_count(Guild) = 2
g->leader = p; // ref_count(Player) = 2
}
// Конец функции. Локальные p и g уничтожаются.
// ref_count(Player) падает до 1.
// ref_count(Guild) падает до 1.
Итог: Функция завершилась. Объекты больше никому в программе не нужны. Но их деструкторы никогда не вызовутся. Они держат друг друга в заложниках, потому что счетчик не упал до нуля. Вы потеряли память.
⚔️ Спаситель:
std::weak_ptrstd::weak_ptr - это умный указатель-наблюдатель. Он умеет смотреть на объект, которым владеет shared_ptr, но не увеличивает его счетчик ссылок.Чтобы разорвать цикл, мы должны определить, кто кем владеет (кто важнее), а кто просто ссылается. Допустим, Гильдия существует независимо от лидера, поэтому она будет просто "наблюдать" за ним.
✅ Правильный код:
struct Guild {
// Слабая ссылка! Не влияет на время жизни Player.
std::weak_ptr<Player> leader;
};
Теперь при выходе из функции счетчик
Player спокойно упадет до нуля. Player удалится. Его деструктор удалит shared_ptr на Гильдию. Счетчик Гильдии упадет до нуля, и она тоже удалится. Чистая победа!👀 Как пользоваться
weak_ptr?Так как
weak_ptr не гарантирует, что объект еще жив (ведь он его не держит), из него нельзя просто так прочитать данные. Вы обязаны превратить его в shared_ptr с помощью метода .lock().
// Если Игрок еще жив, lock() вернет валидный shared_ptr.
// Если Игрок удален, lock() вернет nullptr.
if (std::shared_ptr<Player> ptr = g->leader.lock()) {
std::cout << "Лидер на месте: " << ptr->name;
} else {
std::cout << "Лидер покинул нас...";
}
💡 Золотое архитектурное правило:
Стройте связи в виде дерева.
Сверху вниз - владение (
shared_ptr или unique_ptr).Снизу вверх - наблюдение (
weak_ptr или обычный *, если время жизни жестко гарантировано). Никогда не делайте цикл из shared_ptr.#cpp #memory #smartpointers #leaks #oop #coding #tips
➡️ @cpp_geek
👍9❤4