Clase contenedora de C++ para una matriz de objetos de búfer OpenGL: solo los constructores
Se hizo una pregunta similar aquí , y estoy tratando de hacer lo mismo. Pero
- Estoy intentando un enfoque diferente al derivar de
std::array
, y - Esta es una pregunta muy enfocada solo sobre constructores.
Aquí está mi gl_wrap.h
:
// header guards snipped
#include <array>
#include <algorithm>
#include <cstring>
#include <GL/gl.h>
namespace gl_wrap {
// aliases for style consistency and to mark as parts of gl_wrap
using gl_name = GLuint;
using gl_enum = GLenum;
template<size_t N>
class buffer_objects : public std::array<gl_name, N> {
public:
buffer_objects() noexcept;
~buffer_objects();
buffer_objects(const buffer_objects&) = delete;
buffer_objects operator=(const buffer_objects&) = delete;
buffer_objects(buffer_objects&& from) noexcept;
buffer_objects& operator=(buffer_objects&& from) noexcept;
};
template<size_t N>
buffer_objects<N>::buffer_objects() noexcept
{
glGenBuffers(N, this->data());
}
template<size_t N>
buffer_objects<N>::~buffer_objects()
{
glDeleteBuffers(N, this->data());
}
template<size_t N>
buffer_objects<N>::buffer_objects(buffer_objects<N>&& from) noexcept
: std::array<gl_name, N>(std::move(from))
{
memset(from.data(), 0, N * sizeof(gl_name));
}
template<size_t N>
buffer_objects<N>& buffer_objects<N>::operator=(buffer_objects<N>&& from)
noexcept
{
std::array<gl_name, N>::operator=(std::move(from));
memset(from.data(), 0, N * sizeof(gl_name));
return *this;
}
}
// namespace gl_wrap
Algunas preguntas específicas que tengo sobre este código/enfoque son, si corresponde,
- Decisión de diseño de manejo de errores: ¿deberían los constructores lanzar, o debería dejar la verificación de errores, si lo desea, a la persona que llama?
- Decisión de diseño: ¿es una buena idea usar plantillas aquí? ¿Esto conducirá a un aumento de código problemático si estoy usando muchos de diferentes tamaños
buffer_objects
, o es eficiente? ¿Puedo evaluar esto a través de perfiles? - ¿
memcpy
Vale la pena poner los valores r en un estado no propietario válido, o puedo tratar los valores r como restos no propietarios? - ¿Estoy usando correctamente el constructor de movimiento de la clase base?
Respuestas
Algunas preguntas específicas que tengo sobre este código/enfoque son, si corresponde,
ESTÁ BIEN.
Decisión de diseño de manejo de errores: ¿deberían los constructores lanzar, o debería dejar la verificación de errores, si lo desea, a la persona que llama?
O el objeto está correctamente inicializado y listo para usar o debería lanzarse. La inicialización en dos etapas (construir y luego verificar) es una mala idea ya que deja abierto al usuario para que haga lo correcto. Debe asegurarse de que no se pueda abusar de su objeto y no confiar en que el usuario no abusará de usted.
Decisión de diseño: ¿es una buena idea usar plantillas aquí?
Si vas a consumir std::array
, realmente no tienes otra opción.
¿Esto conducirá a un aumento de código problemático si estoy usando una gran cantidad de buffer_objects de diferentes tamaños, o es eficiente? ¿Puedo evaluar esto a través de perfiles?
Potencialmente, esto conducirá a la compilación de métodos para diferentes tipos. Pero esto depende del compilador y algún compilador moderno puede optimizarlo. Pero, ¿qué estás comparando también?
¿Valen la pena los memcpys para poner los rvalues en un estado no propietario válido, o puedo tratar los rvalues como restos no propietarios?
Creo que necesitas anular los src
valores. De lo contrario, el destructor va a liberar los nombres. Alternativamente, puede almacenar más estados sobre si el objeto es válido y hacer que la llamada esté glDeleteBuffers()
condicionada a que el objeto esté en un estado válido.
Pero también creo que hay un error aquí. Acaba de copiar los valores. Pero no liberó los valores en el dst
lado anterior a la copia, por lo que perdió los nombres almacenados allí.
Recuerde que un std::array no tiene su propio movimiento (ya que los datos son locales para el objeto que no se asignan dinámicamente). Mueve los objetos subyacentes entre contenedores.
¿Estoy usando correctamente el constructor de movimiento de la clase base?
Sí, parece legítimo.
Sugerencias.
Preferiría que std::array
un miembro heredara de él.
Yo lo haría:
#ifndef THORSANVIL_GL_WRAPPER_H
#define THORSANVIL_GL_WRAPPER_H
#include <GL/gl.h>
#include <array>
#include <algorithm>
#include <cstring>
namespace ThorsAnvil::GL {
template<size_t N>
class BufferObjects
{
std::array<GLuint, N> buffer;
bool valid;
public:
BufferObjects() noexcept;
~BufferObjects();
// Note: These are non copyable objects.
// Deleting the copy operations.
BufferObjects(BufferObjects const&) = delete;
BufferObjects operator=(BufferObjects const&) = delete;
// Note: The move is as expensive as a copy operation.
// But we are doing a logical move as you
// can not have two objects with the same resource.
BufferObjects(BufferObjects&& from) noexcept;
BufferObjects& operator=(BufferObjects&& from) noexcept;
// Reset an invalid object.
// Note: If object is valid no action.
void reset();
};
template<size_t N>
BufferObjects<N>::BufferObjects() noexcept
: valid(false)
{
reset();
}
template<size_t N>
BufferObjects<N>::~BufferObjects()
{
if (valid) {
glDeleteBuffers(N, buffer.data());
}
}
template<size_t N>
BufferObjects<N>::BufferObjects(BufferObjects<N>&& from) noexcept
{
// Move the resources from the other object.
std::move(std::begin(from.buffer), std::end(from.buffer), std::begin(buffer));
// Maintain the correct valid states
// The rhs is no longer in a valid state and has no resources.
valid = from.valid;
from.valid = false;
}
template<size_t N>
BufferObjects<N>& BufferObjects<N>::operator=(BufferObjects<N>&& from)
noexcept
{
// The standard copy and swap not efficient.
// So we should do a test for self assignment
if (this != &from)
{
// Destroy then re-create this object.
// Call destructor and then placement new to use
// the move copy constructor code to set this value.
~BufferObjects();
new (this) BufferObjects(std::move(from));
}
return *this;
}
template<size_t N>
void BufferObjects::reset()
{
if (!valid) {
glGenBuffers(N, buffer.data());
valid = true;
}
}