【问题标题】:How to organize object ownership for class that lives lesser time than owner of the object?如何为比对象所有者寿命短的类组织对象所有权?
【发布时间】:2019-12-29 01:00:38
【问题描述】:

我有以下情况:有GraphicsContext类:

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

还有一类应用使用了GraphicsContext:

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

还有一些在 Application 类中使用的子级类和使用 GraphicsContext 中的 Renderer 的子级类。我需要在这些类中存储指向渲染器的指针,但我应该怎么做呢?

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 的原始指针还是语义错误的想法?

【问题讨论】:

  • 如果没有所有权问题,可以使用原始指针。
  • François,但是与在这种情况下使用共享指针相比,它不会导致内存泄漏的可能性吗?
  • SubLevelClass 曾经负责delete 渲染器吗?我的理解是,不,从来都不是。所以一个原始指针很好,你不对它的生命周期负责。 负责的人将拥有像std::unique_ptr&lt;Renderer&gt; 这样的智能指针。如果可能有多个对象负责,则您拥有共享所有权并在那里使用shared_ptr。看起来Renderer 根本不涉及清理,因此不涉及所有权。但也许我误解了你的问题。
  • SubLevelClass 不负责 delete 渲染器。我的意思是当 GraphicsContext 决定 delete 渲染器之前它会在 SubLevelClass 中使用时的情况。在这种情况下,我们将使用已经释放的指针。

标签: c++ pointers memory-management raii ownership-semantics


【解决方案1】:

有一些方法可以解决它。

这些代码没有经过测试,但应该足以展示这个想法。

解决方案 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. 在 ECS 中创建 Renderer 系统。因此,它会最后自动删除。
  2. 使SubLevelClass 成为ECS 中的一个组件。尝试将所有信息(字段、缓存)存储在SubLevelClass - 而不是Renderer。仅这两件事就可以解决您的问题。
  3. 但是,如果它很不走运,例如您需要使 Renderer 不是单例(成为组件):

    3.1 创建一个新组件Component_CheckDelete 例如:-

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

    3.2 每当要删除Renderer 时,只需标记其Component_CheckDelete::ToBeDeleted=true
    然后,在时间步的最后,检查SubLevelClass 的每个实例。
    如果有一些SubLevelClass 引用了具有convertToComponent&lt;Component_CheckDelete&gt;(rendererPtr)-&gt;ToBeDeleted==trueRenderer,则抛出断言失败。

解决方案 3

忽略整个问题。
这是用户方面的错误。引擎创建者不应该抓住每个用户的错误。

Bullet Physics(最好的物理引擎之一)经常使用这种方法 - 如果我删除它的 boardphase 模块并仍然使用它的主引擎,我可能会遇到不负责任的访问违规。

我的看法:我通常选择方案 3,有时选择方案 2,很少选择方案 1A。

【讨论】:

    【解决方案2】:

    std::shared_ptr 是正确的解决方案,恕我直言。

    这样的子级类在语义上并不拥有渲染器,因此我认为使用 shared_ptr 不是一个好主意

    如果子级类需要延长 Renderer 对象的生命周期以匹配它们自己的,那么它们共享它的所有权。

    【讨论】:

      猜你喜欢
      • 2013-09-05
      • 1970-01-01
      • 1970-01-01
      • 2021-05-20
      • 1970-01-01
      • 2020-06-06
      • 2018-06-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多