SeniorКодИногдаЕщё не отвечали
Расширьте реализацию shared_ptr для поддержки weak_ptr.
Дан SharedPtr<T> с подсчётом ссылок и control block с атомарными strong и weak счётчиками. Реализуйте WeakPtr<T>, который наблюдает за объектом, не удерживая его живым.
Ограничения:
с уничтожением последнего SharedPtr; вернуть пустой SharedPtr, если объект мёртв.
WeakPtrвлияет только на weak count, никогда на strong count.lock()обязан атомарно проверить strong > 0 и увеличить его (CAS-цикл), чтобы избежать гонки- Control block должен жить, пока оба счётчика (strong и weak) не обнулятся.
#include <atomic>
template<typename T> class SharedPtr;
template<typename T>
class WeakPtr {
friend class SharedPtr<T>;
public:
WeakPtr() = default;
explicit WeakPtr(const SharedPtr<T>& sp) noexcept;
bool expired() const noexcept;
SharedPtr<T> lock() const noexcept;
// ваш код здесь
};
Допишите реализацию.
WeakPtr<T> разделяет control block, но инкрементирует только weak count. lock() атомарно проверяет strong > 0 и увеличивает через CAS; возвращает пустой SharedPtr, если объект мёртв. Control block живёт до обнуления обоих счётчиков.
- ✗Не использовать CAS-цикл в
lock()— между проверкой count > 0 и его увеличением другой поток может уничтожить последнийSharedPtr; нуженcompare_exchange_weakв цикле - ✗Освобождать управляющий блок при достижении нуля сильным счётчиком —
WeakPtrвсё ещё нуждается в нём; освобождайте только когда оба счётчика равны нулю - ✗Циклическое владение
shared_ptrбезweak_ptr— два объекта, удерживающихshared_ptrдруг на друга, никогда не уничтожаются; разрывайте циклы с помощьюweak_ptr
- →Как
std::enable_shared_from_thisиспользуетweak_ptrвнутри для полученияshared_ptrнаthis? - →Что происходит при вызове
lock()наweak_ptrиз деструктора управляемого объекта?
Оглавление
Расширение SharedPtr до WeakPtr
Обновлённый управляющий блок
#include <atomic>
struct ControlBlock {
std::atomic<int> strong{1};
std::atomic<int> weak{1};
// weak начинается с 1 и уменьшается последним живым SharedPtr.
// Это позволяет SharedPtr не нуждаться в отдельном "weak++" при создании.
// Реальный libc++ использует именно эту технику.
};
WeakPtr
template<typename T>
class SharedPtr; // forward declaration
template<typename T>
class WeakPtr {
friend class SharedPtr<T>;
public:
WeakPtr() = default;
explicit WeakPtr(const SharedPtr<T>& sp) noexcept
: ptr_(sp.ptr_), cb_(sp.cb_) {
if (cb_) cb_->weak.fetch_add(1, std::memory_order_relaxed);
}
WeakPtr(const WeakPtr& other) noexcept
: ptr_(other.ptr_), cb_(other.cb_) {
if (cb_) cb_->weak.fetch_add(1, std::memory_order_relaxed);
}
WeakPtr(WeakPtr&& other) noexcept
: ptr_(other.ptr_), cb_(other.cb_) {
other.ptr_ = nullptr;
other.cb_ = nullptr;
}
WeakPtr& operator=(const WeakPtr& other) noexcept {
WeakPtr tmp(other);
swap(tmp);
return *this;
}
~WeakPtr() { release(); }
bool expired() const noexcept {
return !cb_ || cb_->strong.load(std::memory_order_acquire) == 0;
}
// Атомарно повышает до SharedPtr или возвращает пустой SharedPtr
SharedPtr<T> lock() const noexcept {
if (!cb_) return {};
// CAS: пытаемся увеличить strong, только если > 0
int count = cb_->strong.load(std::memory_order_relaxed);
while (count > 0) {
if (cb_->strong.compare_exchange_weak(count, count + 1,
std::memory_order_acq_rel,
std::memory_order_relaxed)) {
// Успех: возвращаем SharedPtr с уже увеличенным счётчиком
SharedPtr<T> sp;
sp.ptr_ = ptr_;
sp.cb_ = cb_;
return sp;
}
}
return {}; // объект уже уничтожен
}
private:
void release() {
if (!cb_) return;
if (cb_->weak.fetch_sub(1, std::memory_order_acq_rel) == 1)
delete cb_;
cb_ = nullptr;
ptr_ = nullptr;
}
void swap(WeakPtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(cb_, other.cb_);
}
T* ptr_ = nullptr;
ControlBlock* cb_ = nullptr;
};
Обновлённый SharedPtr (деструктор, уменьшает weak)
template<typename T>
class SharedPtr {
friend class WeakPtr<T>;
// ... (весь код из mem-shared-ptr-code, обновлённый release):
void release() {
if (!cb_) return;
if (cb_->strong.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete ptr_;
ptr_ = nullptr;
// Уменьшаем слабый счётчик (он был 1 при создании)
if (cb_->weak.fetch_sub(1, std::memory_order_acq_rel) == 1)
delete cb_;
cb_ = nullptr;
}
}
T* ptr_ = nullptr;
ControlBlock* cb_ = nullptr;
};
Тест
#include <cassert>
struct Obj {
int val;
static int alive;
Obj(int v) : val(v) { ++alive; }
~Obj() { --alive; }
};
int Obj::alive = 0;
int main() {
WeakPtr<Obj> wp;
{
SharedPtr<Obj> sp(new Obj(7));
wp = WeakPtr<Obj>(sp);
assert(!wp.expired());
{
auto locked = wp.lock();
assert(locked);
assert(locked->val == 7);
assert(sp.use_count() == 2);
}
assert(sp.use_count() == 1);
assert(Obj::alive == 1);
}
// sp уничтожен, объект мёртв, cb всё ещё жив из-за wp
assert(wp.expired());
assert(Obj::alive == 0);
auto locked = wp.lock();
assert(!locked); // объект уже уничтожен
}
Оглавление