如何在这种情况下适当使用RAII /减少内存泄漏

时间:2013-02-24 12:37:22

标签: c++ opengl renderer

我不确定如何在我拥有的情况下充分利用RAII。情况就是这样:

我正在创建一个基本的渲染器。 Geometry由Geometry类描述,可以在其中添加顶点。为了使Geometry对象用于渲染,必须首先编译它(即将为Geometry创建VBO)。通过Renderer对象完成几何对象的编译(和解压缩),一旦完成几何对象,就必须进行反编译。如果没有进行反编译,则会出现内存泄漏。

以下是我所描述的一个例子:

Renderer renderer; // the renderer object

{
    Geometry geometry;

    // add vertices
    // and play around with material, etc.

    if(!renderer.compile(geometery); // compile the geometery, so we can use it
    {
        cerr << "Failed to compile geometry!\n";
    }

    // now we can use it...
    renderer.render(geometry, RenderType::TriangleStrips);

} // if I don't call renderer.decompile(geometry) here, I will get a leak

我正在尝试做的是反编译我的几何图形,而没有明确地告诉渲染器反编译它。这只是为了减少内存泄漏。我的第一个想法是使用RAII,但如果我这样做,Geometry类将需要Renderer类,这似乎非常混乱。因为我需要引用编译几何图形的Renderer对象。

我想到的另一个选择是让渲染器创建几何体,但这会导致动态分配几何对象(即使用new),而且它看起来也很混乱。

我还想过在几何图形中放置一个句柄对象,例如对抽象句柄对象的unique_ptr。

e.g。

class GeometeryHandle
{
   virtual ~GeometeryHandle() = 0; 
};

实际上可能有用,因为它也可能用于将GLuint存储在句柄中。我不确定这是否合适,因为我可以直接通过Renderer参考反编译几何体。如果我直接通过析构函数调用它,它会做同样的事情。

我应该如何恰当地设计这个,以免我不小心反编译?

3 个答案:

答案 0 :(得分:3)

方法1:

您可以使用辅助类:

class CompileHandler
{
    private:
        Geometry& g;
        Renderer& r;
    public:
        CompileHandler(Geometry& _g, Renderer& _r) : g(_g), r(_r) 
        {
        }
        ~CompileHandler()
        {
            r.decompile(g);
        }
};

您可以通过以下方式使用它:

{
    Geometry geometry;
    CompileHandler ch(geometry,renderer);

    // add vertices
    // and play around with material, etc.

    if(!renderer.compile(geometery); // compile the geometery, so we can use it
    {
        cerr << "Failed to compile geometry!\n";
    }

    // now we can use it...
    renderer.render(geometry, RenderType::TriangleStrips);
// decompilation is automatic on destruction of the CompileHandler object
}

方法2:

创建更健壮的层次结构:

GeometryCompiler
   ^
   |
   | inherits
   |
Renderer

在编译几何体时,几何编译器(这里是渲染器)通知几何体它已经被编译(并在几何体内部设置一个GeometryCompiler指针,编译器)。 然后,在破坏几何时,如果指针不为null,则可能需要GeometryCompiler对其进行反编译。

答案 1 :(得分:3)

目前尚不清楚谁应该为您的设计负责。这是确定如何采用任何形式的资源管理的第一步:决定谁对什么负责。

例如,您说“Geometry是由几何类描述的,可能会添加顶点。”好的,但这与编译后数据有什么关系?如果用户在编译后将顶点添加到Geometry,那么这些顶点是否会自动放入已编译的数据中?或者编译后的数据在编译后是否与Geometry类完全分开,以致对Geometry类的更改不会更新已编译的数据?

听起来我觉得你把两个截然不同的想法混为一谈:GeometryBuilderRenderableGeometryBuilder是您放置顶点数据的内容。然后你拿出它并从中创建一个RenderableRenderable是存储在GeometryBuilder中的数据的优化形式。该对象完全独立于创建它的GeometryBuilderRenderable是您实际可以呈现的内容。

所以你会这样做:

GeometryBuilder meshBuilder;

meshBuilder.AddVertex(...);
...

Renderable renderMesh = render.CreateRenderable(meshBuilder);

在此之后,meshBuilder独立于renderMesh。你可以删除一个和另一个罚款。您可以多次“编译”renderMesh并获得相同数据的相同副本。等等。

答案 2 :(得分:0)

RAII要求dstructors播放“撤消”操作。

在这种情况下,几何体会被破坏,但渲染仍然存在。你唯一的触发器是Geometry析构函数,它应该知道编译的渲染器,以便让他反编译。

但是因为几何学的目的不是要了解rederes,所以你很可能需要一个辅助类(我们称之为Compile_guard),它应该在几何完成后实例化,将Geometry和Render作为参数,并在构造时调用Render :: compile并在销毁时调用Render :: decompile:

Renderer renderer; // the renderer object

{
    Geometry geometry;

    // add vertices
    // and play around with material, etc.

    Compile_guard guard(render, geometry);

    if(!guard); // Invalid compilation ....
    { 
        cerr << "Failed to compile geometry!\n";
        return; // this is exception safe!
    }

    // now we can use it...
    renderer.render(geometry, RenderType::TriangleStrips);

} //here guard will decompile

关于Compile_guard,它可能类似于

class Compile_guard
{
public:
   Compile_guard(Render& r, Geometry& g) :render(&r), geometry(&g), good(false)
   { good = render->compile(*geometry); }

   ~Compile_guard()
   { if(good) render->decompile(*geometry); }

   explicit operator bool() const { return good; }

   Compile_guard(const Compile_guard&) =delete;  //just avoid copy and assign.
   Compile_guard& operator=(const Compile_guard&) =delete;
private:
   Render* render;
   Geometry* geometry;
   bool good;
};