SeniorКодИногдаЕщё не отвечали
Реализуйте базовый std::shared_ptr с подсчётом ссылок.
Реализуйте базовый SharedPtr<T> с подсчётом ссылок: разделяемое владение объектом в куче, который удаляется, когда уходит последний владелец.
Ограничения:
безопасное при самоприсваивании.
- Храните указатель на объект и heap-allocated control block с атомарным счётчиком ссылок.
- Копирование инкрементирует счётчик; деструктор декрементирует и удаляет объект при нуле.
- Поддержите перемещение (забрать ресурс, оставить источник пустым) и копирующее присваивание,
- Счётчик ссылок обязан быть
std::atomic, чтобы конкурентные копии не вызывали гонок.
#include <atomic>
template<typename T>
class SharedPtr {
public:
explicit SharedPtr(T* ptr = nullptr);
SharedPtr(const SharedPtr& other) noexcept;
SharedPtr(SharedPtr&& other) noexcept;
SharedPtr& operator=(const SharedPtr& other) noexcept;
~SharedPtr();
int use_count() const noexcept;
// ваш код здесь
};
Допишите реализацию.
SharedPtr<T> хранит указатель на объект и указатель на heap-allocated control block со счётчиком std::atomic<int>. Копирование инкрементирует, деструктор декрементирует и удаляет объект при достижении нуля.
- ✗Использовать неатомарный счётчик ссылок — конкурентные копии из разных потоков вызывают гонки на счётчике, приводя к двойному освобождению или утечке
- ✗Хранить счётчик внутри управляемого объекта — не позволяет создать
shared_ptrиз сырого указателя постфактум и усложняет поддержкуweak_ptr - ✗Неправильно обрабатывать самоприсваивание в
operator=— увеличьте новый счётчик до уменьшения старого, чтобы не освободить объект при присваивании самому себе
- →Как работает
std::enable_shared_from_thisвнутри? - →Почему
std::make_sharedиспользует одно выделение, аshared_ptr<T>(new T)— два?
Оглавление
Базовый shared_ptr с подсчётом ссылок
Управляющий блок
#include <atomic>
#include <utility>
struct ControlBlock {
std::atomic<int> strong{1};
std::atomic<int> weak{1}; // +1 от strong; обнуляется при strong == 0
};
Реализация SharedPtr
template<typename T>
class SharedPtr {
public:
// Создание из сырого указателя
explicit SharedPtr(T* ptr = nullptr) {
if (ptr) {
ptr_ = ptr;
cb_ = new ControlBlock{};
}
}
// Копирующий конструктор
SharedPtr(const SharedPtr& other) noexcept
: ptr_(other.ptr_), cb_(other.cb_) {
if (cb_) cb_->strong.fetch_add(1, std::memory_order_relaxed);
}
// Перемещающий конструктор
SharedPtr(SharedPtr&& other) noexcept
: ptr_(other.ptr_), cb_(other.cb_) {
other.ptr_ = nullptr;
other.cb_ = nullptr;
}
// Копирующее присваивание
SharedPtr& operator=(const SharedPtr& other) noexcept {
if (this != &other) {
// Сначала увеличиваем, потом уменьшаем — защита от self-assign через псевдоним
if (other.cb_) other.cb_->strong.fetch_add(1, std::memory_order_relaxed);
release();
ptr_ = other.ptr_;
cb_ = other.cb_;
}
return *this;
}
// Перемещающее присваивание
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
cb_ = other.cb_;
other.ptr_ = nullptr;
other.cb_ = nullptr;
}
return *this;
}
~SharedPtr() { release(); }
T* get() const noexcept { return ptr_; }
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
explicit operator bool() const noexcept { return ptr_ != nullptr; }
int use_count() const noexcept {
return cb_ ? cb_->strong.load(std::memory_order_relaxed) : 0;
}
private:
void release() {
if (!cb_) return;
if (cb_->strong.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete ptr_;
ptr_ = nullptr;
if (cb_->weak.fetch_sub(1, std::memory_order_acq_rel) == 1)
delete cb_;
cb_ = nullptr;
}
}
T* ptr_ = nullptr;
ControlBlock* cb_ = nullptr;
};
// Аналог make_shared (одно выделение)
template<typename T, typename... Args>
SharedPtr<T> makeShared(Args&&... args) {
return SharedPtr<T>(new T(std::forward<Args>(args)...));
}
Тест
#include <cassert>
#include <string>
struct Obj {
int val;
static int alive;
Obj(int v) : val(v) { ++alive; }
~Obj() { --alive; }
};
int Obj::alive = 0;
int main() {
{
SharedPtr<Obj> a(new Obj(42));
assert(a->val == 42);
assert(a.use_count() == 1);
assert(Obj::alive == 1);
SharedPtr<Obj> b = a;
assert(a.use_count() == 2);
assert(b.use_count() == 2);
SharedPtr<Obj> c = std::move(b);
assert(!b);
assert(a.use_count() == 2);
assert(c.use_count() == 2);
}
assert(Obj::alive == 0); // деструктор вызван
}
Оглавление