处理自动资源释放的设计解决方案?

时间:2013-11-01 01:56:41

标签: c++ design-patterns resources

我是一名新的自学成才的编码员,而且我的程序设计和结构一直存在问题。基本问题是释放通过API提供给我的资源(例如,必须使用glDeleteBuffers函数释放的OpenGL缓冲区等),而不会破坏我的系统封装。让我简单介绍一下我的设置:

首先,我的程序使用了一个大型的立面'类称为“引擎”,它包含一些用于图形渲染,音频流,事件处理等的子系统。我的设计背后的主要思想之一是每个子系统都尽可能地封装; OpenGL调用仅存在于图形渲染系统中,OpenAL调用音频等

我还想要小心资源分配和释放,因此不要用新的'来创建各种资产(图像,声音等)。在我的程序的其他部分,我在Engine类中设置了一些创建对象的工厂方法,用适当的子系统注册这些对象(例如,将图像对象引用发送到可以正确缓冲/注册的图形子系统)到OpenGL),并返回指向这些对象的智能指针..

class Engine     //..simplified for sake of example..//
{
private:
    GraphicsSystem graphicsRenderer;
    AudioSystem audioRenderer;
    //..window, events, etc...//

public:
    //ctors.. dtor.. tons of other methods..//

    std::shared_ptr<Image> createImage( /*filepath, size info, other params*/ );

    /*..both of these call equivalent graphics system functions:*/

    bool registerImage( Image & tempImage );    //allocates and registers texture resources..
    bool deregisterImage( Image & tempImage );  //frees those same resources upon object destruction..
};

这是一个工厂方法的示例。

std::shared_ptr<Image> Engine::createImage( /*params*/ )
{
    Image * tempImagePtr = new Image( /*params*/ ); //create..

    registerImage( *tempImagePtr ); //register..

    return std::shared_ptr<Image>( tempImagePtr ); //return..
}

但这是我遇到的主要问题:我发现很难释放使用registerImage函数分配的资源!我想使代码工作,以便客户端只负责从引擎中检索资产对象,资源获取和管理的细节被抽象掉并在资产对象被破坏时自动处理。

以下是我尝试/考虑过的几个选项:

  • 让客户端(Engine类的用户)负责在删除对象之前调用正确的注销功能。这个对象很容易预先编码,但实际上有点糟透,消除了通过工厂方法创建RAII样式对象的要点。这并没有帮助我实现我的目标,即允许客户简单地获取资产并忘记它。

  • 将对整个Engine的单个实例的引用传递给每个资产类,并允许资产类使用该引用在其析构函数内调用注销函数。这是一个我以前尝试过的解决方案,作为一个小小的“hack&#39;确保我有正确的自动资源清理正常工作。它工作〜但我不喜欢让资产访问所有引擎调用的想法;当他们所需要的只是能够使用引擎注册/注销自己时,为每个资产提供那种访问似乎太过分了!

  • 我试图清理我的代码&#39;并通过将指针传递给资产对象的正确注册和注销函数来解决这种情况。这样,对象可以在构造时注册自己,并在销毁时注销自己。在我尝试之前,它似乎是一个很好的计划。到目前为止,我已经非常难以通过资产工厂方法将Engine的非策略方法传递给资产对象! C ++语法是一个令人困惑的混乱,我没有使用函数指针的经验(更不用说分发指向非静态方法的指针)了,它让我感到非常沮丧,我考虑回到马虎,将引用传递给整个Engine实例的抽象策略!我尝试过使用方法名称,我尝试添加&#39;&amp;&#39;在方法名称之前,添加一个&#39; this。&#39;方法名称之前的关键字等

这是我的一个Image资产构建器的样子:

Image( std::string filepath , 
       void (*ptrRegFunc)(unsigned int * tempTextureID, const sf::Image * const tempSourceImage) , 
       void (*ptrDeregFunc)(unsigned int * tempTextureID) );

Image类包含两个相同类型的指针,在我尝试传递指针之前它似乎很好......

std::shared_ptr<Image> Engine::createImage( std::string filePath )
{
    Image * tempImagePtr = new Image( filePath, &registerImage, &deregisterImage ); //create and send func ptrs..

    return std::shared_ptr<Image>( tempImagePtr ); //return..
}

当然,这没有编译,我理解为什么:它不清楚引用哪个Engine对象/实例。但是我已经查了一遍并尝试了所有这些不同的C ++语法风格;

&(this->registerImage)

&((*this).registerImage)

没有任何效果,语法太可怕了,几乎看起来不值得这么麻烦!

但现在我觉得我又回到了方阵;所以我一直在寻找其他可能性。在我看来,这个想法看起来很简单:我只想让每个Asset对象都能够使用Engine对象注册和注销自己。

有没有更好/更简单的方法来实现我的目标而不让每个资产访问整个Engine类接口?我已经阅读了一些关于Command Pattern的内容,听起来这可能是我正在寻找的内容,但我不确定如何实现该模式,或者它真的是我需要的。当然,我也遇到了Function Objects的概念.. 其中任何一个都能解决我的问题吗?

无论如何,谢谢你和我一起讨价还价,我还在学习,我知道这对你们中的许多人来说可能很简单。如果还有其他任何我可以解释我想要什么或我当前系统使用的是什么,请告诉我,我会更新a.s.a.p。

2 个答案:

答案 0 :(得分:1)

这有点高,但看起来这就是你在设计中所处的阶段。

出于某种原因,我认为您可以从专门用于资源管理的课程中受益。这个类可以处理资源的创建和释放,它还可以提供一种重用资源的方法,比如资源池。

答案 1 :(得分:1)

问题中有很多信息,我不确定我是否已正确理解所有信息,但我认为在这种情况下使用shared_ptr来处理RAII的原则听起来不错。如果我读得正确,问题是您要在发布最后一个Engine::deregisterImage()引用时调用Image,对吗?

您是否尝试过使用shared_ptr的自定义删除工具?如果要在Engine::deregisterImage()被销毁之前调用Image,那么您需要某种方式将Engine实例绑定到共享指针。有很多方法可以做到这一点(lambdas,std::bind),但使用仿函数非常简单:

struct ImageDeleter
{
    Engine& engine;
    explicit ImageDeleter(Engine& e) : engine(e) {}

    void operator() (Image* img) {
        engine.deregisterImage(*img);
        delete img;
    }
};

然后在Engine::createImage()方法中,您在shared_ptr构造函数中注册了删除器:

return shared_ptr<Image>(tempImagePtr, ImageDeleter(*this));

现在,当发布最后一个引用时,shared_ptr将调用ImageDeleter::operator(),它应该全部有效。这要求你的Engine实例比它创建的所有图像都要长(否则你会留下一个悬空参考),但无论如何我都会想到这种情况。