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

Что такое счетчик ссылок?

2.0 Middle🔥 191 комментариев
#Язык C++

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

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

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

Счётчик Ссылок: Reference Counting

Счётчик ссылок (Reference Counting) — это механизм управления памятью, при котором каждый объект отслеживает количество ссылок на него. Когда счётчик обнуляется (ссылок больше нет), объект автоматически удаляется из памяти. Это позволяет автоматизировать управление памятью без явного вызова delete.

Основной Принцип

Создание объекта        → Счётчик = 1
Создание новой ссылки   → Счётчик += 1
Удаление ссылки         → Счётчик -= 1
Счётчик = 0             → Объект удаляется из памяти

Реализация в C++

1. Ручная реализация счётчика ссылок

#include <iostream>

class Object {
private:
    int* refCount;
    std::string data;
    
public:
    Object(const std::string& d) : data(d) {
        refCount = new int(1);
        std::cout << "Объект создан, счётчик: " << *refCount << std::endl;
    }
    
    // Copy constructor
    Object(const Object& other) : data(other.data), refCount(other.refCount) {
        (*refCount)++;
        std::cout << "Копия создана, счётчик: " << *refCount << std::endl;
    }
    
    // Copy assignment
    Object& operator=(const Object& other) {
        if (this != &other) {
            (*refCount)--;
            if (*refCount == 0) {
                delete refCount;
            }
            refCount = other.refCount;
            data = other.data;
            (*refCount)++;
        }
        return *this;
    }
    
    ~Object() {
        (*refCount)--;
        std::cout << "Объект удаляется, счётчик: " << *refCount << std::endl;
        if (*refCount == 0) {
            delete refCount;
            std::cout << "Память освобождена" << std::endl;
        }
    }
    
    void print() {
        std::cout << "Data: " << data << ", RefCount: " << *refCount << std::endl;
    }
};

int main() {
    {
        Object obj1("Hello");
        {
            Object obj2 = obj1;  // Счётчик = 2
            obj1.print();
        }  // obj2 удаляется, счётчик = 1
        obj1.print();
    }  // obj1 удаляется, счётчик = 0, память освобождена
    
    return 0;
}

2. std::shared_ptr — встроенный счётчик ссылок

#include <memory>
#include <iostream>

class Resource {
public:
    Resource(const std::string& name) : name(name) {
        std::cout << "Resource " << name << " создан" << std::endl;
    }
    
    ~Resource() {
        std::cout << "Resource " << name << " удалён" << std::endl;
    }
    
    std::string name;
};

int main() {
    {
        std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>("A");
        std::cout << "RefCount: " << ptr1.use_count() << std::endl;  // 1
        
        {
            std::shared_ptr<Resource> ptr2 = ptr1;
            std::cout << "RefCount: " << ptr1.use_count() << std::endl;  // 2
            
            std::shared_ptr<Resource> ptr3 = ptr1;
            std::cout << "RefCount: " << ptr1.use_count() << std::endl;  // 3
        }  // ptr2 и ptr3 удалены
        
        std::cout << "RefCount: " << ptr1.use_count() << std::endl;  // 1
    }  // ptr1 удалён, ресурс освобожден
    
    return 0;
}

3. std::weak_ptr — избежание циклических ссылок

#include <memory>
#include <iostream>

class Node {
public:
    std::string name;
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // Слабая ссылка, не увеличивает счётчик
    
    Node(const std::string& n) : name(n) {
        std::cout << "Node " << name << " создан" << std::endl;
    }
    
    ~Node() {
        std::cout << "Node " << name << " удалён" << std::endl;
    }
};

int main() {
    auto node1 = std::make_shared<Node>("A");
    auto node2 = std::make_shared<Node>("B");
    
    node1->next = node2;
    node2->prev = node1;  // Слабая ссылка — не создаёт цикл
    
    std::cout << "node1 RefCount: " << node1.use_count() << std::endl;  // 2
    std::cout << "node2 RefCount: " << node2.use_count() << std::endl;  // 2
    
    return 0;
}

Проблемы Счётчика Ссылок

1. Циклические ссылки (Circular References)

class A {
public:
    std::shared_ptr<B> b_ref;
    ~A() { std::cout << "A удалён" << std::endl; }
};

class B {
public:
    std::shared_ptr<A> a_ref;
    ~B() { std::cout << "B удалён" << std::endl; }
};

int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    
    a->b_ref = b;  // Счётчик B: 2
    b->a_ref = a;  // Счётчик A: 2
    
    // Утечка памяти!
    return 0;
}

Лучшие Практики

  • Предпочитай unique_ptr когда возможно (один владелец)
  • Используй shared_ptr только при необходимости (множество владельцев)
  • Избегай циклических ссылок или использовать weak_ptr
  • Избегай raw pointers для управления владением памятью
  • Используй make_shared/make_unique для оптимизации