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

Реализация умного указателя shared_ptr

1.7 Middle🔥 161 комментариев
#ООП и проектирование#Умные указатели и управление памятью#Язык C++

Условие

Реализуйте упрощённую версию std::shared_ptr с подсчётом ссылок.

Ваш класс должен поддерживать:

  • Конструктор от указателя
  • Конструктор копирования
  • Оператор присваивания копированием
  • Деструктор, освобождающий память когда счётчик достигает нуля
  • Операторы * и -> для доступа к объекту
  • Метод use_count() для получения текущего счётчика ссылок
  • Метод get() для получения сырого указателя

Требования

  • Счётчик ссылок должен быть общим для всех копий shared_ptr
  • Корректная работа с nullptr
  • Потокобезопасность НЕ требуется (для упрощения)

Пример использования

{
    MySharedPtr<int> p1(new int(42));
    std::cout << p1.use_count() << std::endl; // 1
    
    {
        MySharedPtr<int> p2 = p1;
        std::cout << p1.use_count() << std::endl; // 2
        std::cout << *p2 << std::endl; // 42
    }
    
    std::cout << p1.use_count() << std::endl; // 1
} // память освобождается здесь

Бонус

Добавьте поддержку move-семантики (move constructor и move assignment).

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

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

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

Решение: Реализация умного указателя shared_ptr

Концепция

shared_ptr использует подсчёт ссылок (reference counting) для автоматического управления памятью:

  • Каждый shared_ptr указывает на блок контрольной информации (control block)
  • Control block содержит: указатель на объект, счётчик ссылок, функцию-deleter
  • При копировании shared_ptr счётчик увеличивается
  • При разрушении счётчик уменьшается, и если он 0 — объект удаляется

Полная реализация

#include <iostream>
#include <utility>

template<typename T>
class MySharedPtr {
private:
    // Блок контрольной информации
    struct ControlBlock {
        T* ptr;
        int refCount;
        
        explicit ControlBlock(T* p) : ptr(p), refCount(1) {}
    };
    
    ControlBlock* block;
    
    void addRef() {
        if (block) {
            ++block->refCount;
        }
    }
    
    void release() {
        if (!block) return;
        
        --block->refCount;
        if (block->refCount == 0) {
            delete block->ptr;
            delete block;
        }
    }
    
public:
    // Конструктор от указателя
    explicit MySharedPtr(T* ptr = nullptr) {
        if (ptr) {
            block = new ControlBlock(ptr);
        } else {
            block = nullptr;
        }
    }
    
    // Конструктор копирования
    MySharedPtr(const MySharedPtr& other) : block(other.block) {
        addRef();
    }
    
    // Оператор присваивания копированием
    MySharedPtr& operator=(const MySharedPtr& other) {
        if (this == &other) {
            return *this;
        }
        
        release();  // освобождаем старый объект
        block = other.block;
        addRef();   // увеличиваем счётчик нового
        
        return *this;
    }
    
    // Конструктор перемещения (БОНУС)
    MySharedPtr(MySharedPtr&& other) noexcept : block(other.block) {
        other.block = nullptr;  // обнуляем источник
    }
    
    // Оператор присваивания перемещением (БОНУС)
    MySharedPtr& operator=(MySharedPtr&& other) noexcept {
        if (this == &other) {
            return *this;
        }
        
        release();
        block = other.block;
        other.block = nullptr;
        
        return *this;
    }
    
    // Деструктор
    ~MySharedPtr() {
        release();
    }
    
    // Оператор разыменования
    T& operator*() const {
        return *block->ptr;
    }
    
    // Оператор стрелка
    T* operator->() const {
        return block->ptr;
    }
    
    // Получить счётчик ссылок
    int use_count() const {
        return block ? block->refCount : 0;
    }
    
    // Получить сырой указатель
    T* get() const {
        return block ? block->ptr : nullptr;
    }
    
    // Проверка на nullptr
    explicit operator bool() const {
        return block != nullptr;
    }
    
    // Для юнит-тестов: получить адрес контрольного блока
    ControlBlock* _getBlock() const {
        return block;
    }
};

Использование и примеры

int main() {
    // Пример 1: базовое использование
    {
        MySharedPtr<int> p1(new int(42));
        std::cout << "p1.use_count(): " << p1.use_count() << std::endl;  // 1
        std::cout << "*p1: " << *p1 << std::endl;  // 42
    }
    // p1 разрушен, память освобождена
    
    // Пример 2: копирование
    {
        MySharedPtr<int> p1(new int(100));
        MySharedPtr<int> p2 = p1;  // копирование
        MySharedPtr<int> p3(p1);   // конструктор копирования
        
        std::cout << "use_count: " << p1.use_count() << std::endl;  // 3
        std::cout << "*p2: " << *p2 << std::endl;  // 100
        std::cout << "*p3: " << *p3 << std::endl;  // 100
    }
    // Все три разрушены, память освобождена
    
    // Пример 3: структуры
    struct Person {
        std::string name;
        int age;
        
        Person(const std::string& n, int a) : name(n), age(a) {
            std::cout << "Person created: " << name << std::endl;
        }
        
        ~Person() {
            std::cout << "Person destroyed: " << name << std::endl;
        }
    };
    
    {
        MySharedPtr<Person> p1(new Person("Alice", 30));
        MySharedPtr<Person> p2 = p1;
        
        std::cout << p1->name << ", age " << p1->age << std::endl;  // Alice, age 30
        std::cout << "use_count: " << p1.use_count() << std::endl;    // 2
    }
    // Выведет: Person destroyed: Alice
    
    // Пример 4: перемещение (БОНУС)
    {
        MySharedPtr<int> p1(new int(777));
        MySharedPtr<int> p2 = std::move(p1);  // move assignment
        
        std::cout << "p1.use_count(): " << p1.use_count() << std::endl;  // 0 (nullptr)
        std::cout << "p2.use_count(): " << p2.use_count() << std::endl;  // 1
        std::cout << "*p2: " << *p2 << std::endl;  // 777
    }
    
    // Пример 5: nullptr
    {
        MySharedPtr<int> p1;
        MySharedPtr<int> p2(nullptr);
        
        std::cout << "p1.use_count(): " << p1.use_count() << std::endl;  // 0
        std::cout << "p2.use_count(): " << p2.use_count() << std::endl;  // 0
        std::cout << "bool(p1): " << static_cast<bool>(p1) << std::endl; // false
    }
    
    return 0;
}

Трассировка примера

MySharedPtr<int> p1(new int(42));
// block = new ControlBlock(ptr) 
// block->refCount = 1
MySharedPtr<int> p2 = p1;
// block скопирован
// block->refCount = 2
MySharedPtr<int> p3(std::move(p2));
// Конструктор перемещения:
// p3.block = p2.block
// p2.block = nullptr
// block->refCount остаётся 2 (не меняется!)
~p2();  // разрушение p2
// block == nullptr, поэтому release() ничего не делает
~p1();  // разрушение p1
// --block->refCount  (2 -> 1)
~p3();  // разрушение p3
// --block->refCount  (1 -> 0)
// delete block->ptr;  освобождение объекта
// delete block;       освобождение контрольного блока

Ключевые детали

1. Control Block — единственный источник истины:

struct ControlBlock {
    T* ptr;        // указатель на объект
    int refCount;  // счётчик ссылок (общий для всех копий)
};

2. Move semantics НЕ увеличивает счётчик:

MySharedPtr(MySharedPtr&& other) : block(other.block) {
    other.block = nullptr;  // просто передаём владение
}

3. Безопасность копирования:

operator=(const MySharedPtr& other) {
    if (this == &other) return *this;  // self-assignment
    release();      // сначала удаляем старое
    block = other.block;
    addRef();       // затем берём новое
}

4. Отсутствие утечек:

release():
    --refCount
    if (refCount == 0) {
        delete ptr;    // удаляем объект
        delete block;  // удаляем контрольный блок
    }

Анализ сложности

  • Конструктор: O(1) — создание control block
  • Копирование: O(1) — инкремент счётчика
  • Перемещение: O(1) — просто копирование указателя
  • Доступ (*, ->): O(1) — разыменование
  • Память: O(1) дополнительно на control block

Различия с std::shared_ptr

ОсобенностьНаша реализацияstd::shared_ptr
СложностьO(1)O(1)
ПотокобезопасностьНЕТДА (atomic счётчик)
make_shared оптимизацияНЕТДА (один allocation)
Поддержка aliasing constructorНЕТДА
Пользовательский deleterНЕТДА
Наследование template параметраНЕТДА (конвертация типов)

Потенциальные улучшения

1. Поддержка наследования:

template<typename U>
MySharedPtr(const MySharedPtr<U>& other) : block(other._getBlock()) {
    addRef();
}

2. Пользовательский deleter:

struct ControlBlock {
    std::function<void(T*)> deleter;
};

3. Потокобезопасность (std::atomic):

std::atomic<int> refCount;  // вместо int

4. make_shared оптимизация: Один allocation вместо двух для ptr и control block.