← Назад к вопросам

Как получить утечку памяти из умного указателя?

2.0 Middle🔥 171 комментариев
#Умные указатели и управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ: Циклические ссылки и неправильное использование 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)

Умные указатели - отличный инструмент, но требуют понимания их семантики!