easy-gl › Ownership & RAII

Ownership & RAII

Move-only resources

Every resource class in easy-gl is non-copyable and movable. Copying is deleted because duplicating an OpenGL handle silently leads to double-free bugs when both copies are destroyed.

easygl::Buffer a;
a.create();

// ERROR — copy is deleted:
easygl::Buffer b = a;

// OK — ownership transferred, a is now empty:
easygl::Buffer b = std::move(a);
// a.is_created() == false now

RAII lifecycle

The lifecycle has three phases:

PhaseHowState
EmptyAfter default construction or after move-fromis_created() == false, handle == 0
CreatedAfter calling create()is_created() == true, valid GL handle
DestroyedDestructor runs, or destroy() called explicitlyGL handle freed, object back to empty state
{
    easygl::Texture tex;
    // tex.is_created() == false

    tex.create();
    // tex.is_created() == true, GL handle allocated

    tex.bind(easygl::TextureTarget::Texture2D);
    // use tex ...

} // tex.~Texture() called — GL handle freed automatically

Explicit destroy()

You can release a resource early without waiting for the destructor:

tex.destroy();
// tex.is_created() == false — handle freed, object reusable

tex.create(); // fine — allocate a new handle
destroy() is noexcept. Calling it on an already-empty object is safe.

Move semantics in detail

Move construction transfers ownership; the source becomes empty:

easygl::Buffer loadBuffer(const float* data, std::size_t bytes)
{
    easygl::Buffer buf;
    buf.create();
    buf.bind(easygl::BufferTarget::Array);
    buf.set_data(easygl::BufferTarget::Array, data, bytes);
    return buf; // NRVO or move — no copy
}

easygl::Buffer vbo = loadBuffer(vertices, sizeof(vertices));
// vbo owns the GL handle; local buf inside the function is gone

Generation tracking

Every resource has a creation_generation() counter and an is_valid_for_current_generation() check. This is designed for applications that recreate the OpenGL context (e.g. mobile context loss).

When a context is lost and recreated, all GPU handles become invalid. Generation tracking lets you detect stale handles without storing a separate "dirty" flag.

if (!vbo.is_valid_for_current_generation()) {
    vbo.reset_handle_no_gl(); // discard stale handle (no GL call)
    vbo.create();            // reallocate with new context
    // re-upload data...
}
reset_handle_no_gl() clears the stored handle without calling any GL function. Use it only after a context loss event when all previous handles are already invalid at the driver level.

Storing resources in containers

Because resources are move-only, use std::move when inserting into containers:

std::vector<easygl::Texture> textures;

easygl::Texture t;
t.create();
textures.push_back(std::move(t));
// t is now empty; textures[0] owns the handle

native_handle()

For interoperability with code that needs the raw OpenGL integer handle:

unsigned int raw_id = vbo.native_handle();
// Pass to legacy code, imgui, or other libraries that want raw GL ids
Do not call glDelete* on handles you got from native_handle(). The resource class still owns the handle and will try to delete it in its destructor.