Подтвердить что ты не робот

Реализация суицидальных объектов с использованием `std:: weak_ptr`

Я рассматриваю возможность использования "объектов самоубийства" для моделирования объектов в игре, то есть объектов, способных удалять себя. Теперь обычная реализация С++ 03 (простой старый delete this) ничего не делает для других объектов, которые потенциально ссылаются на объект самоубийства, поэтому я использую std::shared_ptr и std::weak_ptr.

Теперь для дампа кода:

#include <memory>
#include <iostream>
#include <cassert>

struct SuObj {
    SuObj() { std::cout << __func__ << '\n'; }
    ~SuObj() { std::cout << __func__ << '\n'; }

    void die() {
        ptr.reset();
    }

    static std::weak_ptr<SuObj> create() {
        std::shared_ptr<SuObj> obj = std::make_shared<SuObj>();
        return (obj->ptr = std::move(obj));
    }

private:

    std::shared_ptr<SuObj> ptr;
};

int main() {
    std::weak_ptr<SuObj> obj = SuObj::create();

    assert(!obj.expired());
    std::cout << "Still alive\n";

    obj.lock()->die();

    assert(obj.expired());
    std::cout << "Deleted\n";

    return 0;
}

Вопрос

Этот код работает нормально. Тем не менее, я бы хотел, чтобы кто-то еще посмотрел на него. Имеет ли смысл этот код? Я слепо плавал в землях? Должен ли я отказаться от клавиатуры и начать заниматься искусством прямо сейчас?

Надеюсь, этот вопрос достаточно сузился для SO. Считается немного крошечным и низкоуровневым для CR.

Незначительная точность

Я не намерен использовать это в многопоточном коде. Если возникнет такая необходимость, я обязательно передумаю все это.

4b9b3361

Ответ 1

Когда у вас есть время жизни объекта на основе shared_ptr, время жизни вашего объекта - это "время жизни" объединения shared_ptr, владеющего им коллективно.

В вашем случае у вас есть внутренний shared_ptr, и ваш объект не умрет, пока не истечет срок выполнения внутренней shared_ptr.

Однако это не означает, что вы можете совершить самоубийство. Если вы удалите эту последнюю ссылку, ваш объект будет продолжать существовать , если у кого есть .lock() 'd weak_ptr и сохранен результат. Поскольку это единственный способ получить доступ к объекту извне, может случиться 1.

Короче говоря, die() может не убить объект. Его можно было бы назвать remove_life_support(), поскольку что-то еще могло сохранить объект в живых после удаления поддержки жизни.

Кроме этого, ваш дизайн работает.


1 Вы могли бы сказать "хорошо, тогда вызывающие должны просто не поддерживать shared_ptr вокруг", но это не работает, поскольку проверка того, что объект действителен, действителен только в том случае, если сохраняется shared_ptr. Кроме того, раскрывая способ создания shared_ptr, у вас нет гарантий типа, что клиентский код не будет хранить их (случайно или по назначению).

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

Или вы можете жить с объектом, который иногда живет слишком долго.


Рассмотрите возможность скрытия этих беспорядочных деталей за регулярным типом (или почти регулярным), который имеет pImpl для неприятной проблемы управления памятью. Это pImpl может быть weak_ptr с указанной семантикой.

Затем пользователям вашего кода необходимо взаимодействовать только с обычной (или псевдорегулярной) оболочкой.

Если вы не хотите, чтобы клонирование было легким, отключите построение/назначение копии и выведите только перемещение.

Теперь ваше неприятное управление памятью скрывается за лабиринтом, и если вы решите, что все это неправильно, внешний интерфейс pseudoRegular может иметь разные кишки.

Обычный тип в С++ 11

Ответ 2

Не прямой ответ, но потенциально полезная информация.

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

Я попытался его реализовать и выяснил, что проблему двойного удаления можно решить, перейдя во внутренний shared_ptr пользовательский пустой дебетер - с этого момента ни один shared_ptrs, созданный из weak_ptr не внутренний shared_ptr, будет иметь возможность вызвать деструктор (снова) на свой объект.

Единственная проблема для решения - что, если ваш объект удален, а где-то еще вы сохраняете shared_ptr? Но из того, что я вижу, это не может быть просто решена каким-либо магическим знанием и требует разработки всего проекта так, как это просто никогда не происходит, например. используя shared_ptr только в локальной области и гарантируя, что некоторый набор операций (создание объекта самоубийства, используя его, заказывающий его самоубийство) может выполняться только в той же теме.

Ответ 3

Я понимаю, что вы пытаетесь создать минимальный пример для SO, но я вижу несколько проблем, которые вы захотите рассмотреть:

  • У вас есть открытый конструктор и деструктор, поэтому технически там нет гарантии, что метод create() всегда используется.
  • Вы можете создавать защищенные или частные, но это решение будет мешать использованию с std-алгоритмами и контейнерами.
  • Это не гарантирует, что объект будет фактически разрушаться, поскольку до тех пор, пока кто-то имеет shared_ptr, он будет существовать. Это может быть или не быть проблемой для вашего варианта использования, но из-за этого я не думаю, что это добавит столько же значения, сколько вы заголовок.
  • Это, скорее всего, будет запутанным и противоречивым для других разработчиков. Это может затруднить техническое обслуживание, даже если вы намерены сделать это проще. Это немного ценностное суждение, но я бы посоветовал вам рассмотреть, действительно ли это проще управлять.

Я рекомендую вам задуматься над управлением памятью. Дисциплинированное использование shared_ptr и weak_ptr поможет с проблемами управления памятью - я бы советовал не пытаться, чтобы экземпляр попытался управлять собственным жизненным циклом.

Что касается художественных исследований... Я бы рекомендовал только, если это действительно ваша страсть! Удачи!