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:
| Phase | How | State |
|---|---|---|
| Empty | After default construction or after move-from | is_created() == false, handle == 0 |
| Created | After calling create() | is_created() == true, valid GL handle |
| Destroyed | Destructor runs, or destroy() called explicitly | GL 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
glDelete* on handles you got from native_handle(). The resource class still owns the handle and will try to delete it in its destructor.