Как освобождается память при отсутствии сильных ссылок?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Управление памятью со слабыми ссылками в C++
Этот вопрос относится к системе управления памятью через shared_ptr и weak_ptr в C++. Это умный подход, который автоматизирует освобождение памяти.
Концепция Reference Counting
C++ использует подсчёт ссылок (reference counting) для определения, когда можно удалить объект.
#include <memory>
#include <iostream>
class Resource {
public:
~Resource() {
std::cout << "Resource удалена!" << std::endl;
}
};
int main() {
// shared_ptr хранит счётчик ссылок
std::shared_ptr<Resource> ptr1(new Resource());
std::cout << "После создания ptr1: " << ptr1.use_count() << " ссылок" << std::endl;
// Вывод: 1 ссылка
{
std::shared_ptr<Resource> ptr2 = ptr1; // Копируем ссылку
std::cout << "После создания ptr2: " << ptr1.use_count() << " ссылок" << std::endl;
// Вывод: 2 ссылки
}
// ptr2 уходит из scope
std::cout << "После уничтожения ptr2: " << ptr1.use_count() << " ссылок" << std::endl;
// Вывод: 1 ссылка
return 0;
// ptr1 уходит из scope, счётчик обнуляется
// Resource автоматически удаляется - выводит "Resource удалена!"
}
Слабые ссылки (weak_ptr)
weak_ptr — это "наблюдатель" за объектом, который НЕ увеличивает счётчик ссылок. Используется для избежания циклических ссылок.
#include <memory>
#include <iostream>
class Node {
public:
int value;
std::shared_ptr<Node> next; // сильная ссылка
std::weak_ptr<Node> previous; // слабая ссылка - не препятствует удалению
Node(int v) : value(v) {}
~Node() {
std::cout << "Node " << value << " удалена" << std::endl;
}
};
int main() {
auto node1 = std::make_shared<Node>(1);
auto node2 = std::make_shared<Node>(2);
node1->next = node2;
node2->previous = node1; // weak_ptr не удерживает node1
std::cout << "node1.use_count() = " << node1.use_count() << std::endl; // 2
std::cout << "node2.use_count() = " << node2.use_count() << std::endl; // 1
// node2 использует только 1 сильную ссылку, хотя на него указывают 2 узла
return 0;
// Обе ноды удаляются без циклических ссылок
}
Проблема циклических ссылок
ДО: Циклические ссылки приводят к утечке памяти
class Person {
public:
std::string name;
std::shared_ptr<Person> friend_ptr;
~Person() {
std::cout << "Person " << name << " удалена" << std::endl;
}
};
int main() {
{
auto alice = std::make_shared<Person>();
alice->name = "Alice";
auto bob = std::make_shared<Person>();
bob->name = "Bob";
alice->friend_ptr = bob; // alice указывает на bob
bob->friend_ptr = alice; // bob указывает на alice
// use_count(alice) = 2 (alice + bob)
// use_count(bob) = 2 (bob + alice)
}
// alice выходит из scope, но счётчик = 1 (bob держит ссылку)
// bob выходит из scope, но счётчик = 1 (alice держит ссылку)
// УТЕЧКА ПАМЯТИ! Обе ссылки > 0, никогда не удалятся
return 0;
}
ПОСЛЕ: Используем weak_ptr для одной ссылки
class Person {
public:
std::string name;
std::shared_ptr<Person> best_friend; // сильная ссылка
std::weak_ptr<Person> acquaintance; // слабая ссылка
~Person() {
std::cout << "Person " << name << " удалена" << std::endl;
}
};
int main() {
{
auto alice = std::make_shared<Person>();
alice->name = "Alice";
auto bob = std::make_shared<Person>();
bob->name = "Bob";
alice->best_friend = bob; // use_count(bob) = 2
bob->acquaintance = alice; // use_count(alice) = 1 (weak_ptr не считается)
}
// bob выходит из scope, use_count(bob) = 1, удаляется
// alice выходит из scope, use_count(alice) = 0, удаляется
return 0;
}
Как использовать weak_ptr
#include <memory>
class EventListener {
private:
std::weak_ptr<EventEmitter> emitter; // не удерживаем владение
public:
EventListener(std::shared_ptr<EventEmitter> e) : emitter(e) {}
void on_event() {
// Попробуем получить сильную ссылку
if (auto strong = emitter.lock()) {
// Объект ещё существует, можем его использовать
strong->emit();
} else {
// Объект уже удалён
std::cout << "Emitter был удалён" << std::endl;
}
}
};
Механизм освобождения памяти
struct MemoryBlock {
void* data; // данные пользователя
int strong_count = 0; // счётчик shared_ptr
int weak_count = 0; // счётчик weak_ptr
void increment_strong() { strong_count++; }
void decrement_strong() {
strong_count--;
if (strong_count == 0) {
delete_data(); // Удаляем объект
if (weak_count == 0) {
delete_control_block(); // Удаляем управляющий блок
}
}
}
};
int main() {
{
std::shared_ptr<Resource> ptr1(new Resource());
// strong_count = 1
std::weak_ptr<Resource> weak = ptr1;
// weak_count = 1, strong_count = 1
}
// ptr1 уходит из scope
// strong_count = 0 -> удаляется Resource
// weak_count = 1 -> управляющий блок остаётся
// weak_ptr ещё может проверить, существует ли объект
return 0;
}
Практический пример: Observer Pattern
class Subject {
private:
std::vector<std::weak_ptr<Observer>> observers;
public:
void attach(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
}
void notify() {
// Удаляем мёртвые ссылки
auto it = observers.begin();
while (it != observers.end()) {
if (auto obs = it->lock()) {
obs->update();
++it;
} else {
it = observers.erase(it); // Observer удалён, удаляем из списка
}
}
}
};
Правила использования
Используйте shared_ptr когда:
- Нужна общая ответственность за объект
- Объект может жить дольше создателя
std::shared_ptr<Resource> ptr = std::make_shared<Resource>();
Используйте weak_ptr когда:
- Вы не хотите препятствовать удалению объекта
- Предотвращаете циклические ссылки (parent-child)
- Реализуете pattern Observer, Cache, Listener
std::weak_ptr<Resource> weak = ptr;
Используйте unique_ptr когда:
- Единственный владелец объекта
- Объект удаляется в конце scope
std::unique_ptr<Resource> ptr(new Resource());
Вывод
Память освобождается автоматически когда счётчик сильных ссылок (shared_ptr) обнуляется. Слабые ссылки (weak_ptr) позволяют наблюдать за объектом без удержания его в памяти. Это предотвращает циклические ссылки и утечки памяти.