So, the straight forward approach would be something like this (untested):
class Texture
{
public:
Texture():
_id{0}
{
glGenTextures(1, &_id);
if (!*this)
{
throw std::runtime_error("Failed to acquire texture.");
}
}
~Texture()
{
if (*this)
{
glDeleteTextures(1, &_id);
}
}
explicit operator bool() const
{
return _id != 0;
}
// Non-Copyable
Texture(Texture const& other) = delete;
Texture& operator=(Texture const& other) = delete;
// Moveable
Texture(Texture&& other):
_id{0}
{
swap(*this, other);
}
Texture& operator=(Texture&& other)
{
swap(*this, other);
return *this;
}
// Wrapper Methods
//void bind();
//void image(...);
//voud subImage(...);
private:
GLuint _id;
friend void swap(Texture& lhs, Texture& rhs)
{
using std::swap;
swap(lhs._id, rhs._id);
}
};
Initially this works fine, but it tries to do things at the same time: provide wrapper methods and manage lifetime. This becomes a problem when you receive a handle from the C api, whose lifetime is already managed in some other way. In that case, you cannot use the wrapper methods. For example, this isn't possible:
Texture getBoundTexture()
{
GLuint boundTexture = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*) &boundTexture);
return {boundTexture};
}
Even if there was a matching constructor, this code would be buggy since now there would be two RAII objects managing one lifetime. When one of them is destroyed, the resource gets freed and the second destructor results in double deletion. Really, it should be possible to distinguish between objects that manage lifetimes and those that don't. Well, you might say there already is such a thing: smart pointers! I could create my Texture object on the heap and use smart pointers to manage its lifetime. However, there is really no need for creating objects on the heap, if instead we generalize smart pointers. In fact, this has been proposed. However, it is not part of the standard library yet and - what I think is even worse - it's not easily possible to associate wrapped methods with the handle.
So, instead I have come up with the following approach (which I am sure has its own problems, that's why I am looking for feedback):
First, lifetime management is encapsulated in a separate class (similar to the proposed unique_resource):
template<typename T>
class Unique
{
public:
template<typename... Args>
Unique(Args&&... args):
_value{}
{
T::New(_value, std::forward<Args>(args)...);
if (!_value)
{
throw std::runtime_error("Failed to acquire resource.");
}
}
~Unique()
{
if (_value)
{
T::Delete(_value);
}
}
Unique(Unique const& other) = delete;
Unique& operator=(Unique const& other) = delete;
Unique(Unique&& other):
_value{}
{
swap(*this, other);
}
Unique& operator=(Unique&& other)
{
swap(*this, other);
return *this;
}
T& operator*() { return _value; }
T const& operator*() const { return _value; }
T* operator->() { return &_value; }
T const* operator->() const { return &_value; }
private:
T _value;
friend void swap(Unique& lhs, Unique& rhs)
{
using std::swap;
swap(lhs._value, rhs._value);
}
};
And the wrappers look like this:
class Texture
{
public:
typedef GLuint Handle;
static void New(Texture& object)
{
glGenTextures(1, &object._handle);
}
static void Delete(Texture& object)
{
glDeleteTextures(1, &object._handle);
}
Texture(): _handle{0} {}
Texture(Handle const& handle): _handle{handle} {}
Handle const& get() const { return _handle; }
explicit operator bool() const
{
return _handle != 0;
}
// Wrapper Methods
//void bind();
//void image(...);
//voud subImage(...);
private:
Handle _handle;
friend void swap(Texture& lhs, Texture& rhs)
{
using std::swap;
swap(lhs._handle, rhs._handle);
}
};
The usage could be as follows (artificial example):
{
Texture bound = getBoundTexture(); // Imlementation as before, now works.
Unique<Texture> managed;
//Setup texture etc.
//managed->image(...);
managed->bind();
// Draw Something
bound.bind();
}
So, the wrappers are like a plain pointer, whereas if you want their lifetime managed, you should use Unique<Texture>. At the end of the block, the manage texture is destroyed, whereas the plain one is untouched. Of course, it would also be possible to implement a Shared<Texture> in the future.
Anyway, I am curious to hear your thoughts. Please critique away...