I need a unique_ptr and shared_ptr like structure, but instead of pointers, I would like to store some kind of reference to a resource. It is usually just an int and maybe some attributes. I would like to avoid using pointers, so these objects can be kept on the stack.
I usually use these containers in my OpenGL projects or with some C libraries, where I call a C function to allocate a resource, and then get back an ID or opaque pointer. If I do not need that resource anymore, then I have to call the appropriate deallocator function. In this case, the deallocator is called by the container.
ResourceOwner:
template<typename T>
class ResourceOwner {
T data;
protected:
ResourceOwner() : data() {}
public:
// Disable copy
ResourceOwner(const ResourceOwner&) = delete;
ResourceOwner &operator=(const ResourceOwner&) = delete;
// Move constructor
ResourceOwner(ResourceOwner&& other) noexcept : data(other.Release())
{}
// Move operator
ResourceOwner &operator=(ResourceOwner&& other) noexcept
{
Reset(other.Release());
return *this;
}
// Delete resource
void Reset(T&& new_data = T())
{
data.Reset();
data = std::move(new_data);
}
// Get raw data
const T& Get() const {
return data;
}
~ResourceOwner()
{
Reset();
}
private:
// Release ownership
T Release()
{
T ret = std::move(data);
data = T();
return ret;
}
};
The reference counter is a pointer in the SharedResourceOwner, but it is only created, when the second instance is created. The resource reference is not a pointer, because it is immutable, so it can be copied between shared owners.
SharedResourceOwner:
template<typename T>
class SharedResourceOwner {
// number of owners except this
mutable size_t* reference_counter = nullptr;
// raw resource data
T data = T();
protected:
SharedResourceOwner() {}
public:
~SharedResourceOwner() {
Reset();
}
// Copy constructor
SharedResourceOwner(const SharedResourceOwner& other) noexcept {
other.IncreaseCounter();
reference_counter = other.reference_counter;
data = other.data;
}
// Copy operator
SharedResourceOwner &operator=(const SharedResourceOwner& other) noexcept {
if (&other != this) {
Reset();
other.IncreaseCounter();
reference_counter = other.reference_counter;
data = other.data;
}
return *this;
}
// Move constructor
SharedResourceOwner(SharedResourceOwner &&other) noexcept
{
reference_counter = other.reference_counter;
data = std::move(other.data);
other.reference_counter = nullptr;
other.data = T();
}
// Move operator
SharedResourceOwner &operator=(SharedResourceOwner &&other) noexcept
{
Reset();
reference_counter = other.reference_counter;
data = std::move(other.data);
other.reference_counter = nullptr;
other.data = T();
return *this;
}
// delete resource
void Reset(T&& new_data = T()) {
if (reference_counter == nullptr) {
data.Reset();
}
else if (*reference_counter == 0) {
delete reference_counter;
data.Reset();
}
else {
(*reference_counter)--;
}
reference_counter = nullptr;
data = std::move(new_data);
}
// Get raw data
const T& Get() const {
return data;
}
private:
void IncreaseCounter() const {
if (reference_counter == nullptr) {
reference_counter = new size_t(0);
}
reference_counter++;
}
};
The type T is a "ResourceReference" type. It is immutable inside the container. The content of the container may be reset or replaced, but its attributes cannot be changed, therefore you are only able to get a constant reference to it with Get()
Example class
An example type T looks like this:
struct TextureData {
GLuint texture_id = 0;
GLenum target = GL_TEXTURE_2D;
TextureData() {}
TextureData(GLuint texture_id) : texture_id(texture_id) {}
void Reset() {
if (texture_id > 0) {
glDeleteTextures(1, &texture_id);
}
}
};
The contained classes are really simple, they only have some data members, and Reset is not expected to throw exception.
An example container:
class Texture : public ResourceOwner<TextureData> {
public:
Texture() : ResourceOwner() {}
GLuint GetId() const {
return Get().texture_id;
}
// static helper function which returns a new image loaded from the path
// defined in Texture.cpp
static Texture FromFile(std::string path);
};
Usage of the example class
app.hpp:
#include "Texture.hpp"
class App {
Texture myTexture;
// ...
void Init();
void Render();
// ...
}
app.cpp:
void App::Init() {
myTexture = Texture::FromFile("example.jpg");
// ...
}
void App::Render() {
// ...
glActiveTexture(GL_TEXTURE0);
glBindTexture(texture.Get().target, myTexture.GetId());
SetUniform(tex_uniform_id, 0);
// ...
}
Example Texture creation:
Texture createTextureFromData(
std::vector<uint8_t> data,
GLsizei width = 0,
GLsizei height = 0
) {
Texture texture
TextureData texture_data;
// Generate OpenGL texture
glGenTextures(1, &texture_data.texture_id);
glBindTexture(GL_TEXTURE_2D, texture_data.texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
// Generate mipmap
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
//
texture.Reset(std::move(texture_data));
return texture;
}
glVertexAttribPointer. \$\endgroup\$