← Назад к вопросам
Как предотвратить циклическую зависимость с помощью std::weak_ptr?
2.0 Middle🔥 241 комментариев
#Умные указатели и управление памятью
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
std::weak_ptr для предотвращения циклических зависимостей
Проблема циклических ссылок
struct Node {
std::shared_ptr<Node> next;
};
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2; // node1 -> node2
node2->next = node1; // node2 -> node1 (ЦИКЛ)
// Когда выходим из scope, оба объекта НЕ удаляются
// node1 имеет node2 на reference (ref_count = 2)
// node2 имеет node1 на reference (ref_count = 2)
// Оба говорят друг другу: "ты должен остаться для меня"
// Утечка памяти (memory leak)
Решение: std::weak_ptr
weak_ptr НЕ увеличивает reference count:
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // Не увеличивает ref_count
};
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2; // node1 -> node2 (ref_count[node2] = 2)
node2->prev = node1; // node2 -weak-> node1 (ref_count[node1] = 1)
// Когда выходим из scope:
// node1 удалится (ref_count[node1] = 0)
// Тогда node2 удалится (ref_count[node2] = 0)
// БЕЗ утечки
Как использовать weak_ptr
std::weak_ptr<Node> weakPtr = node1;
// Чтобы использовать, нужно преобразовать в shared_ptr
if (auto shared = weakPtr.lock()) {
// объект ещё существует
shared->process();
} else {
// объект уже удалён
std::cout << "Object was deleted\n";
}
Практический пример: двусвязный список
struct ListNode {
int value;
std::shared_ptr<ListNode> next;
std::weak_ptr<ListNode> prev; // Слабая ссылка на предыдущий
};
class DoublyLinkedList {
public:
void append(int value) {
auto newNode = std::make_shared<ListNode>();
newNode->value = value;
if (tail) {
newNode->prev = tail; // Слабая ссылка
tail->next = newNode;
} else {
head = newNode;
}
tail = newNode;
}
void printForward() {
auto node = head;
while (node) {
std::cout << node->value << " -> ";
node = node->next;
}
std::cout << "null\n";
}
void printBackward() {
auto node = tail;
while (node) {
std::cout << node->value << " <- ";
if (auto prev = node->prev.lock()) {
node = prev;
} else {
break;
}
}
std::cout << "null\n";
}
private:
std::shared_ptr<ListNode> head;
std::shared_ptr<ListNode> tail;
};
Observer паттерн с weak_ptr
class Observer {
public:
virtual void update() = 0;
virtual ~Observer() = default;
};
class Subject {
private:
std::vector<std::weak_ptr<Observer>> observers;
public:
void subscribe(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
}
void notify() {
// Удаляем мёртвые ссылки
auto it = std::remove_if(
observers.begin(), observers.end(),
[](const auto& obs) { return obs.expired(); }
);
observers.erase(it, observers.end());
// Уведомляем живые
for (auto& obs : observers) {
if (auto shared = obs.lock()) {
shared->update();
}
}
}
};
Граф с циклами
struct GraphNode {
int value;
std::vector<std::shared_ptr<GraphNode>> children;
std::weak_ptr<GraphNode> parent; // Слабая ссылка на родителя
};
auto root = std::make_shared<GraphNode>();
auto child = std::make_shared<GraphNode>();
root->children.push_back(child);
child->parent = root; // Слабая ссылка, без цикла
Когда использовать weak_ptr
✅ Используй weak_ptr для:
- Parent-child отношений (child -> parent = weak)
- Observer паттерна (subject не владеет observers)
- Кеша (кеш не должен держать объекты живыми)
- Обратных ссылок в графах
❌ Не используй weak_ptr:
- Вместо shared_ptr основного владения
- Если не нужны циклические ссылки
- В простых случаях (используй просто значения)
Потенциальные проблемы
Race condition:
auto sharedPtr = weakPtr.lock(); // Получили shared_ptr
// Между lock() и использованием объект может быть удалён
if (sharedPtr) {
sharedPtr->use(); // OK, объект существует
}
Забыли lock():
weakPtr->use(); // ❌ Ошибка компиляции! weak_ptr нельзя разыменовать напрямую
// Правильно:
if (auto sp = weakPtr.lock()) {
sp->use(); // OK
}
Лучшие практики
// 1. Используй shared_ptr для основного владения
auto ptr = std::make_shared<Object>();
// 2. Используй weak_ptr для обратных ссылок
struct Parent { };
struct Child {
std::weak_ptr<Parent> parent;
};
// 3. Всегда проверяй expired() или используй lock()
if (!weakPtr.expired()) {
if (auto sp = weakPtr.lock()) {
sp->use();
}
}
// 4. Очищай мёртвые ссылки
observers.erase(
std::remove_if(observers.begin(), observers.end(),
[](auto& w) { return w.expired(); }),
observers.end()
);
Производительность
- shared_ptr: быстро
- weak_ptr: немного медленнее (extra bookkeeping)
- Но намного быстрее чем утечка памяти
Заключение
- shared_ptr для владения ресурсом
- weak_ptr для неопасных ссылок
- weak_ptr предотвращает утечки в циклических структурах
- Всегда lock() перед использованием
- Проверяй expired() перед операциями