среда, 19 ноября 2014 г.

Как устроен shared_ptr

Для чего нужен

shared_ptr нужен для ситуации, когда имеются несколько ссылок на объект, созданных в динамической памяти. При этом освобождать объект нужно тогда, когда удаляется последняя ссылка. И делать это нужно безопасно даже в многопоточном коде.

Как отслеживать ссылки на объект

Каждый объект shared_ptr содержит созданный в динамической памяти счётчик ссылок, который увеличивается на единицу, когда shared_ptr завладевает объектом, и уменьшается на единицу, когда shared_ptr перестаёт владеть объектом.
Код объекта счётчика ссылок может выглядеть так:

template <typename T>
class sp_counted {
public:
    explicit sp_counted(T *p) noexcept : count(1), ptr(p) {}

    void add_ref() noexcept {
        ++count;
    }

    void release() noexcept {
        if (!--count) {
            delete ptr;
            delete this;
        }
    }

    size_t use_count() const noexcept {
        return count.load();
    }

private:
    std::atomic<size_t> count;
    T *ptr;
};
При этом код shared_ptr может выглядеть так:
template <typename T>
class shared_ptr {
public:
    shared_ptr() noexcept : ptr(nullptr), counted(nullptr) {}

    // excaption safe конструктор
    explicit shared_ptr(T *p) {
        std::unique_ptr<T> holder(p);
        // new может кинуть исключение, и, если p не передать в unique_ptr,
        // память под p потеряется
        counted = new sp_counted<T>(holder.get());
        ptr = holder.release();
    }

    ~shared_ptr() noexcept {
        release();
    }

    shared_ptr(const shared_ptr &other) noexcept : ptr(other.ptr), counted(other.counted) {
        add_ref();
    }

    shared_ptr &operator=(const shared_ptr &other) noexcept {
        // Освобождаем владение предыдущим указателем
        release();

        // Выполняем присваивание
        ptr = other.ptr;
        counted = other.counted;

        // Устанавливаем владение новым указателем
        add_ref();

        // Ура! Я не забыл вернуть *this!
        return *this;
    }

    T *get() const noexcept {
        return ptr;
    }

    size_t use_count() const noexcept {
        return counted != nullptr ? counted->use_count() : 0;
    }

private:
    void add_ref() {
        if (counted) {
            counted->add_ref();
        }
    }

    void release() {
        if (counted) {
            counted->release();
        }
    }

private:
    T *ptr;
    sp_counted<T> *counted;
};

А что с потокобезопасностью?

Любая операция с shared_ptr потокобезопасна, если каждый поток хранит копию shared_ptr. При этом каждая копия может хранить указатель на один объект.
Хранить ссылки на один shared_ptr в разных потоках непотокобезопасно.

Комментариев нет:

Отправить комментарий