Как получить утечку памяти из умного указателя?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ: Циклические ссылки и неправильное использование shared_ptr
Умные указатели обычно предотвращают утечки памяти, но есть несколько способов их получить, если не быть осторожным. Самая частая причина - циклические ссылки в shared_ptr.
Проблема 1: Циклические ссылки (Circular references)
Это классическая проблема с shared_ptr:
class Node {
public:
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev;
int data;
};
int main() {
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
// Циклическая ссылка!
node1->next = node2;
node2->prev = node1;
// node1 и node2 выходят из scope
// Но они НЕ удаляются!
// Утечка памяти в 1024 байта (size of Node * 2)
}
// Почему? Reference counting:
// node1 имеет refcount = 2 (переменная + node2->prev)
// node2 имеет refcount = 2 (переменная + node1->next)
// Когда переменные выходят из scope, refcount = 1
// Но они никогда не достигают 0, поэтому объекты не удаляются
Решение: weak_ptr
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // WEAK указатель!
int data;
};
int main() {
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
node1->next = node2;
node2->prev = node1; // weak_ptr не увеличивает refcount
// Когда node1 и node2 выходят из scope, они удаляются
// Потому что refcount становится 0
} // OK! Память освобождена
Важно: weak_ptr НЕ увеличивает reference count!
Проблема 2: Захват this в лямбде
class EventHandler {
public:
void setup_listener(EventBus& bus) {
// ❌ Утечка!
bus.on_event([this](const Event& e) {
this->handle(e);
});
}
private:
void handle(const Event& e) {}
};
int main() {
std::shared_ptr<EventHandler> handler = std::make_shared<EventHandler>();
EventBus bus;
handler->setup_listener(bus);
// handler выходит из scope, но лямбда остаётся в bus
// Лямбда захватила this, поэтому объект не удаляется
// Утечка: объект невозможно удалить
} // handler выходит из scope, но не удаляется!
Решение: использовать weak_ptr
class EventHandler : public std::enable_shared_from_this<EventHandler> {
public:
void setup_listener(EventBus& bus) {
// ✅ Правильно
auto weak_this = std::weak_ptr<EventHandler>(shared_from_this());
bus.on_event([weak_this](const Event& e) {
if (auto self = weak_this.lock()) {
self->handle(e);
}
});
}
private:
void handle(const Event& e) {}
};
Проблема 3: Кольцевые зависимости в сложных структурах
class Parent {
public:
std::shared_ptr<Child> child;
};
class Child {
public:
std::shared_ptr<Parent> parent; // Циклическая ссылка!
};
int main() {
std::shared_ptr<Parent> parent = std::make_shared<Parent>();
parent->child = std::make_shared<Child>();
parent->child->parent = parent; // Циклическая ссылка
// Утечка! parent и parent->child никогда не удаляются
} // Оба объекта остаются в памяти
Решение: parent класс использует shared_ptr, child использует weak_ptr
class Parent {
public:
std::shared_ptr<Child> child;
};
class Child {
public:
std::weak_ptr<Parent> parent; // weak_ptr!
};
int main() {
std::shared_ptr<Parent> parent = std::make_shared<Parent>();
parent->child = std::make_shared<Child>();
parent->child->parent = parent; // Теперь OK
// Когда parent выходит из scope, он удаляется
// parent->child удаляется потому что refcount = 0
} // OK! Память освобождена
Проблема 4: Передача shared_ptr в std::function
class Server {
public:
std::function<void()> callback;
void set_callback(std::shared_ptr<Server> self) {
// ❌ Утечка: std::function захватывает shared_ptr
callback = [self]() {
std::cout << "Callback" << std::endl;
};
}
};
int main() {
std::shared_ptr<Server> server = std::make_shared<Server>();
server->set_callback(server); // Циклическая ссылка!
// server->callback содержит лямбду с self=server
// refcount = 2, никогда не упадёт до 0
} // Утечка!
Решение: использовать слабую ссылку
class Server : public std::enable_shared_from_this<Server> {
public:
std::function<void()> callback;
void set_callback() {
auto weak_self = std::weak_ptr<Server>(shared_from_this());
callback = [weak_self]() {
if (auto self = weak_self.lock()) {
std::cout << "Callback" << std::endl;
}
};
}
};
Проблема 5: new вместо make_shared
// ❌ Менее эффективно
std::shared_ptr<MyClass> p(new MyClass()); // 2 allocations
// ✅ Лучше
std::shared_ptr<MyClass> p = std::make_shared<MyClass>(); // 1 allocation
Это не совсем утечка, но make_shared более эффективен.
Проблема 6: Забыть удалить listener
class Component {
public:
void init(EventBus& bus) {
bus.subscribe(std::make_shared<Listener>());
// ❌ Listener остаётся в памяти навсегда
// Нет способа его удалить!
}
};
Решение: сохранить подписку
class Component {
private:
std::shared_ptr<Subscription> subscription;
public:
void init(EventBus& bus) {
subscription = bus.subscribe(std::make_shared<Listener>());
// Теперь можно удалить subscription -> unsubscribe
}
void cleanup() {
subscription.reset(); // Удалить подписку
}
};
Общие правила для избежания утечек
✅ Используй weak_ptr для обратных ссылок - parent->child (shared), child->parent (weak) ✅ Используй enable_shared_from_this - если нужен shared_ptr на себя в методе ✅ Избегай захвата shared_ptr в лямбдах - используй weak_ptr ✅ RAII pattern - очищай ресурсы в деструкторе ✅ Тестируй утечки - используй valgrind или Address Sanitizer ✅ Используй make_shared вместо new - одно выделение вместо двух ✅ Слушатели/обсёрверы - сохраняй подписку, чтобы потом отписаться
Как найти утечку
# Address Sanitizer
g++ -fsanitize=address program.cpp
./a.out # Покажет утечки
# Valgrind
valgrind --leak-check=full ./program
# Clang memory profiler
clang++ -fprofile-instr-generate program.cpp
Итог
❌ Не защищает от утечек:
- Циклические ссылки (shared_ptr -> shared_ptr)
- Захват this в долгоживущих лямбдах
- Кольцевые зависимости между объектами
✅ Решение:
- Используй weak_ptr для обратных ссылок
- Используй enable_shared_from_this для захвата в лямбде
- Думай о зависимостях: owner (shared) и owned (weak)
Умные указатели - отличный инструмент, но требуют понимания их семантики!