← Назад к вопросам
Реализация умного указателя 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.