Как организовать владение объектом для класса, который живет меньше времени, чем владелец объекта?

У меня такая ситуация: есть класс GraphicsContext:

class GraphicsContext {
    ...
private:
    std::unique_ptr<Renderer> m_renderer;
}

И есть класс приложения, которое использует GraphicsContext:

class Application {
   ...
private:
    std::unique_ptr<GraphicsContext> m_graphicsContext;
}

И есть классы подуровня, которые используются в классе Application и которые используют Renderer из GraphicsContext. Мне нужно сохранить указатель на средство визуализации в этих классах, но как мне это сделать?

class SubLevelClass {
public:
    SubLevelClass(Renderer* renderer);
    ...
    void drawSomething();
private:
    Renderer* m_renderer; // this class is not owner of Renderer but should can ability to refer to it
}

Такие классы подуровня семантически не владеют средством визуализации, и поэтому я думаю, что использовать shared_ptr вместо unique_ptr - не лучшая идея. Но как организовать такое владение, если гарантировано, что объекты подуровневых классов живут меньше времени, чем объект Application? Могу ли я сохранить и вернуть из GraphicsContext необработанный указатель на Renderer или это семантически неправильная идея?


person Nikolai Paukov    schedule 23.08.2019    source источник
comment
Необработанные указатели подходят, если нет проблем с правом собственности.   -  person François Andrieux    schedule 23.08.2019
comment
Возможный дубликат: Всегда ли разумно использовать указатели?   -  person François Andrieux    schedule 23.08.2019
comment
Франсуа, а не приводит ли это к возможности утечки памяти по сравнению с использованием общего указателя в таких ситуациях?   -  person Nikolai Paukov    schedule 23.08.2019
comment
SubLevelClass когда-либо отвечал за delete обработку рендерера? Насколько я понимаю, нет, этого никогда не было. Так что с необработанным указателем все в порядке, вы не несете ответственности за его время жизни. Тот, кто несет ответственность, будет обладать умным указателем, например std::unique_ptr<Renderer>. Если ответственность может быть несколько, значит, у вас совместное владение и вы используете shared_ptr. Похоже, что Renderer вообще не связан с очисткой, поэтому не связан с владением. Но, возможно, я неправильно понял ваш вопрос.   -  person François Andrieux    schedule 23.08.2019
comment
SubLevelClass не несет ответственности за delete обработку рендерера. Я имею в виду ситуацию, когда GraphicsContext решает delete использовать рендерер до того, как он будет использован в SubLevelClass. В этом случае мы получим использование уже освобожденного указателя.   -  person Nikolai Paukov    schedule 23.08.2019
comment
Чтобы убедиться, что Renderer* m_renderer; не является висящим указателем, вы можете использовать assertion. Всякий раз, когда экземпляр Renderer удаляется - проверьте, есть ли еще SubLevelClass его сохранить. Для отслеживания требуется некоторая дополнительная структура данных (например, карта).   -  person javaLover    schedule 24.08.2019
comment
javaLover, если я правильно понимаю, этот подход требует уведомить всех SubLevelClasses об удалении Renderer экземпляра? И я сомневаюсь в применимости утверждений, потому что в C ++ я не могу проверить достоверность необработанного указателя во время выполнения. Я могу только проверить его на ненулевое значение, но в данном случае этого недостаточно.   -  person Nikolai Paukov    schedule 25.08.2019
comment
Извините, я не получил уведомления о вашем ответе. Вы можете добавить @ like @JavaLover в комментарии, чтобы пинговать меня. :)   -  person javaLover    schedule 01.09.2019


Ответы (2)


Есть несколько способов решить эту проблему.

Эти коды не проверены, но их должно быть достаточно, чтобы показать идею.

Решение 1.Просто отслеживайте

Решение 1A: -

class Renderer{
    std::vector<SubLevelClass*> whoThatLinkBackToMe; //or std::unordered_set
    public: ~Renderer(){
        //assert that whoThatLinkBackToMe.size() == 0
    }
};
class SubLevelClass{
    SubLevelClass(Renderer* renderer){
        renderer->whoThatLinkBackToMe.push_back(this);
    }
    //.. destructor should remove "this" from "renderer->whoThatLinkBackToMe" too
};

Решение 1B: -

class CentralizeSystem{
    public: static std::unordered_map<Renderer*,SubLevelClass*> map;
};
class Renderer{
    public: ~Renderer(){
        //assert that CentralizeSystem::map[this].size() == 0
    }
};
class SubLevelClass{
    SubLevelClass(Renderer* renderer){
        CentralizeSystem::map.add(renderer,this);
    }
    //.. destructor should remove "this" from "CentralizeSystem::map" too
};

Решение 2. Система компонентов сущности (ECS)

Это революция в дизайне, требующая огромных усилий:

  1. Сделайте Renderer Системой в ECS. Таким образом, он автоматически удаляется последним.
  2. Сделайте SubLevelClass компонентом в ECS. Постарайтесь хранить всю информацию (поле, кеш) в SubLevelClass, а не в Renderer. Только эти 2 вещи должны решить вашу проблему.
  3. Однако, если это очень не повезло, например, вам нужно сделать Renderer не singleton (стать Component):

    3.1 Создайте новый компонент Component_CheckDelete, например. : -

    class Component_CheckDelete : public Component{
        public: bool ToBeDeleted=false;
    };
    

    3.2 Каждый раз, когда нужно удалить Renderer, просто отметьте его Component_CheckDelete::ToBeDeleted=true.
    Затем, в конце временного шага, проверьте каждый экземпляр SubLevelClass.
    Если есть некоторые SubLevelClass, которые относятся к Renderer с convertToComponent<Component_CheckDelete>(rendererPtr)->ToBeDeleted==true, бросить утверждение сбой.

Решение 3

Просто проигнорируйте всю проблему.
Это ошибка на стороне пользователя. Создатель движка не должен улавливать каждую ошибку пользователя.

Bullet Physics (один из лучших Physics Engine) часто использует этот подход - если я удалю его модуль Boardphase и все еще буду использовать его основной движок, я могу получить безответственное нарушение прав доступа.

Мое мнение: я обычно выбираю Решение 3, иногда Решение 2, редко выбираю Решение 1A.

person javaLover    schedule 01.09.2019

std::shared_ptr - правильное решение здесь imho.

Такие классы подуровня семантически не владеют средством визуализации, и поэтому я думаю, что использовать shared_ptr - не лучшая идея.

Если классам подуровня необходимо продлить время жизни объекта Renderer, чтобы оно соответствовало их собственному, то они разделяют владение им.

person bolov    schedule 01.09.2019