Класс-оболочка C ++ для массива буферных объектов OpenGL - только конструкторы
Аналогичный вопрос был задан здесь , и я пытаюсь сделать то же самое. Но
- Я пробую другой подход, исходя из
std::array
, и - Это очень конкретный вопрос только о конструкторах.
Вот мой 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
Некоторые конкретные вопросы, которые у меня есть по поводу этого кода / подхода, если применимо,
- Конструкторское решение обработки ошибок: должны ли конструкторы выдавать ошибку, или я должен оставить проверку ошибок, если это необходимо, вызывающей стороне?
- Дизайнерское решение: стоит ли здесь использовать шаблоны? Приведет ли это к проблемному раздуванию кода, если я использую много разных размеров
buffer_objects
, или это эффективно? Могу ли я оценить это с помощью профилирования? memcpy
Стоит ли s переводить rvalues в допустимое состояние без владения, или я могу рассматривать rvalue как остатки без владения?- Правильно ли я использую конструктор перемещения базового класса?
Ответы
Некоторые конкретные вопросы, которые у меня есть по поводу этого кода / подхода, если применимо,
ОК.
Конструкторское решение обработки ошибок: должны ли конструкторы выдавать ошибку, или я должен оставить проверку ошибок, если это необходимо, вызывающей стороне?
Либо объект правильно инициализирован и готов к использованию, либо его следует выбросить. Двухэтапная инициализация (построение и проверка) - плохая идея, так как оставляет пользователю возможность делать правильные действия. Вы должны убедиться, что вашим объектом нельзя злоупотреблять, не полагайтесь на то, что пользователь не оскорбит вас.
Дизайнерское решение: стоит ли здесь использовать шаблоны?
Если вы собираетесь использовать, у std::array
вас действительно нет выбора.
Приведет ли это к проблемному раздутию кода, если я использую много объектов buffer_object разного размера, или это эффективно? Могу ли я оценить это с помощью профилирования?
Возможно, это приведет к компиляции методов для разных типов. Но это зависит от компилятора, и некоторые современные компиляторы могут это оптимизировать. Но что вы тоже сравниваете?
Стоит ли memcpys переводить rvalues в допустимое состояние без владения, или я могу рассматривать rvalue как остатки без владения?
Я думаю, вам нужно обнулить src
значения. В противном случае деструктор освободит имена. В качестве альтернативы вы можете сохранить больше данных о том, действителен ли объект, и сделать вызов glDeleteBuffers()
условным, если объект находится в допустимом состоянии.
Но я тоже думаю, что здесь есть ошибка. Вы только что скопировали значения. Но вы не освободили значения на dst
стороне перед копией, поэтому вы потеряли сохраненные там имена.
Помните, что std :: array не имеет собственного перемещения (поскольку данные являются локальными для объекта, который не выделяется динамически). Он перемещает базовые объекты между контейнерами.
Правильно ли я использую конструктор перемещения базового класса?
Да, похоже, лигит.
Предложения.
Я бы сделал std::array
член скорее унаследованным от него.
Я бы сделал:
#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;
}
}