Класс-оболочка C ++ для массива буферных объектов OpenGL - только конструкторы

Aug 18 2020

Аналогичный вопрос был задан здесь , и я пытаюсь сделать то же самое. Но

  1. Я пробую другой подход, исходя из std::array, и
  2. Это очень конкретный вопрос только о конструкторах.

Вот мой 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

Некоторые конкретные вопросы, которые у меня есть по поводу этого кода / подхода, если применимо,

  1. Конструкторское решение обработки ошибок: должны ли конструкторы выдавать ошибку, или я должен оставить проверку ошибок, если это необходимо, вызывающей стороне?
  2. Дизайнерское решение: стоит ли здесь использовать шаблоны? Приведет ли это к проблемному раздуванию кода, если я использую много разных размеров buffer_objects, или это эффективно? Могу ли я оценить это с помощью профилирования?
  3. memcpyСтоит ли s переводить rvalues ​​в допустимое состояние без владения, или я могу рассматривать rvalue как остатки без владения?
  4. Правильно ли я использую конструктор перемещения базового класса?

Ответы

5 MartinYork Aug 18 2020 at 04:43

Некоторые конкретные вопросы, которые у меня есть по поводу этого кода / подхода, если применимо,

ОК.

Конструкторское решение обработки ошибок: должны ли конструкторы выдавать ошибку, или я должен оставить проверку ошибок, если это необходимо, вызывающей стороне?

Либо объект правильно инициализирован и готов к использованию, либо его следует выбросить. Двухэтапная инициализация (построение и проверка) - плохая идея, так как оставляет пользователю возможность делать правильные действия. Вы должны убедиться, что вашим объектом нельзя злоупотреблять, не полагайтесь на то, что пользователь не оскорбит вас.

Дизайнерское решение: стоит ли здесь использовать шаблоны?

Если вы собираетесь использовать, у 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;
    }
}