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

Как освобождается память при отсутствии сильных ссылок?

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

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

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

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

Управление памятью со слабыми ссылками в 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) позволяют наблюдать за объектом без удержания его в памяти. Это предотвращает циклические ссылки и утечки памяти.