Classe wrapper C++ pour un tableau d'objets tampon OpenGL - uniquement les constructeurs
Une question similaire a été posée ici , et j'essaie de faire la même chose. Mais
- J'essaie une approche différente en dérivant de
std::array
, et - Il s'agit d'une question très ciblée sur les constructeurs uniquement.
Voici mon 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
Certaines questions spécifiques que j'ai au sujet de ce code/approche sont, le cas échéant,
- Décision de conception de la gestion des erreurs : les constructeurs doivent-ils lancer ou dois-je laisser la vérification des erreurs, si nécessaire, à l'appelant ?
- Décision de conception : est-ce une bonne idée d'utiliser des modèles ici ? Cela conduira-t-il à un gonflement problématique du code si j'utilise beaucoup de tailles différentes
buffer_objects
, ou est-ce efficace? Puis-je évaluer cela via le profilage ? - Les
memcpy
s valent-ils la peine de mettre les rvalues dans un état non propriétaire valide, ou puis-je traiter les rvalues comme des restes non propriétaires? - Est-ce que j'utilise correctement le constructeur de déplacement de la classe de base ?
Réponses
Certaines questions spécifiques que j'ai au sujet de ce code/approche sont, le cas échéant,
D'ACCORD.
Décision de conception de la gestion des erreurs : les constructeurs doivent-ils lancer ou dois-je laisser la vérification des erreurs, si nécessaire, à l'appelant ?
Soit l'objet est correctement initialisé et prêt à l'emploi, soit il doit lancer. L'initialisation en deux étapes (construction puis vérification) est une mauvaise idée car elle laisse la possibilité à l'utilisateur de faire ce qu'il faut. Vous devez vous assurer que votre objet ne peut pas être abusé et ne pas compter sur l'utilisateur pour ne pas vous abuser.
Décision de conception : est-ce une bonne idée d'utiliser des modèles ici ?
Si vous allez utiliser std::array
, vous n'avez pas vraiment le choix.
Cela conduira-t-il à un gonflement problématique du code si j'utilise beaucoup d'objets tampons de tailles différentes, ou est-ce efficace ? Puis-je évaluer cela via le profilage ?
Cela conduira potentiellement à compiler des méthodes pour différents types. Mais cela dépend du compilateur et certains compilateurs modernes peuvent optimiser cela. Mais qu'est-ce que tu compares aussi ?
Les memcpys valent-ils la peine de mettre les rvalues dans un état non propriétaire valide, ou puis-je traiter les rvalues comme des restes non propriétaires?
Je pense qu'il faut annuler les src
valeurs. Sinon, le destructeur va libérer les noms. Alternativement, vous pouvez stocker plus d'état pour savoir si l'objet est valide et rendre l'appel glDeleteBuffers()
conditionnel à ce que l'objet soit dans un état valide.
Mais je pense aussi qu'il y a un bug ici. Vous venez de recopier les valeurs. Mais vous n'avez pas publié les valeurs sur le dst
côté avant la copie, vous avez donc perdu les noms qui y sont stockés.
N'oubliez pas qu'un std :: array n'a pas son propre mouvement (car les données sont locales à l'objet non alloué dynamiquement). Il déplace les objets sous-jacents entre les conteneurs.
Est-ce que j'utilise correctement le constructeur de déplacement de la classe de base ?
Oui, il semble ligit.
Suggestions.
J'en ferais std::array
plutôt hériter un membre.
Je ferais:
#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;
}
}