2

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

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

这是我所描述的示例:

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 类,这看起来很混乱。因为我需要一个对编译几何图形的渲染器对象的引用。

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

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

例如

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

这实际上可能有效,因为它也可以用于存储GLuint手柄内。我不确定这是否合适,因为我可以直接通过渲染器引用简单地反编译几何。即如果我直接通过析构函数调用它,它会做同样的事情。

我应该如何适当地设计它,以免我不小心不反编译几何?

4

3 回答 3

3

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

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

在我看来,您将两个非常不同的想法混为一谈:GeometryBuilderRenderable. GeometryBuilder是您将顶点数据放入的内容。然后你把它Renderable拿出来创造一个。是Renderable存储在GeometryBuilder. 该对象完全独立于GeometryBuilder创建它的对象。这Renderable是您实际可以渲染的内容。

所以你会这样做:

GeometryBuilder meshBuilder;

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

Renderable renderMesh = render.CreateRenderable(meshBuilder);

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

于 2013-02-24T13:12:38.510 回答
3

方法一:

您可以使用辅助类:

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
}

方法二:

创建更健壮的层次结构:

GeometryCompiler
   ^
   |
   | inherits
   |
Renderer

在几何体编译时,几何体编译器(这里是渲染器)通知几何体它已经被编译(并GeometryCompiler在几何体内部设置一个指向它的指针,编译器)。然后,在破坏几何时,如果指针不为空,则可以要求GeometryCompiler反编译它。

于 2013-02-24T13:00:30.960 回答
0

RAII 要求 dstructors 执行“撤消”操作。

在您的情况下,几何体被破坏,但渲染仍然存在。您拥有的唯一触发器是几何析构函数,它应该知道编译了什么渲染,调用他进行反编译。

但是由于几何的目的不是要了解 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;
};
于 2013-02-24T13:08:46.250 回答