OpenGL 버퍼 객체 배열을위한 C ++ 래퍼 클래스 — 생성자 만

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. memcpyrvalue를 유효한 비 소유 상태에 두는 것이 가치가 있습니까 , 아니면 rvalue를 비 소유 잔여 물로 취급 할 수 있습니까?
  4. 기본 클래스의 이동 생성자를 올바르게 사용하고 있습니까?

답변

5 MartinYork Aug 18 2020 at 04:43

이 코드 / 접근법에 대한 몇 가지 구체적인 질문은 해당되는 경우

확인.

오류 처리 설계 결정 : 생성자가 발생해야합니까, 아니면 원하는 경우 호출자에게 오류 검사를 남겨야합니까?

개체가 올바르게 초기화되어 사용할 준비가되었거나 throw되어야합니다. 2 단계 초기화 (구성 후 확인)는 올바른 작업을 수행하기 위해 사용자에게 열려 있기 때문에 나쁜 생각입니다. 당신은 당신의 물건이 남용되지 않도록해야합니다.

디자인 결정 : 여기서 템플릿을 사용하는 것이 좋은 생각입니까?

사용하려는 경우 std::array선택의 여지가 없습니다.

크기가 다른 buffer_objects를 많이 사용하는 경우 문제가있는 코드 부풀림이 발생합니까, 아니면 효율적입니까? 프로파일 링을 통해 이것을 평가할 수 있습니까?

잠재적으로 이로 인해 다른 유형에 대해 메서드가 컴파일됩니다. 그러나 이것은 컴파일러에 따라 다르며 일부 최신 컴파일러는이를 최적화 할 수 있습니다. 그러나 당신도 그것을 비교하고 있습니까?

memcpys는 rvalue를 유효한 비 소유 상태에 놓을 가치가 있습니까, 아니면 rvalue를 비 소유 잔여 물로 취급 할 수 있습니까?

src값 을 무효화해야한다고 생각 합니다. 그렇지 않으면 소멸자가 이름을 공개 할 것입니다. 또는 개체가 유효한지 여부에 대해 더 많은 상태를 저장 glDeleteBuffers()하고 유효한 상태에있는 개체에 대해 조건부 호출을 수행 할 수 있습니다.

하지만 여기에도 버그가 있다고 생각합니다. 방금 값을 복사했습니다. 그러나 dst사본 전에 측면 의 값을 해제하지 않았 으므로 거기에 저장된 이름이 손실되었습니다.

std :: array에는 자체 이동이 없습니다 (데이터가 동적으로 할당되지 않은 객체에 로컬이기 때문에). 컨테이너간에 기본 개체를 이동합니다.

기본 클래스의 이동 생성자를 올바르게 사용하고 있습니까?

예, 그것은 ligit처럼 보입니다.

제안.

나는 std::arraya 멤버를 오히려 상속 하도록 만들 것입니다.

나는 할것이다:

#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;
    }
}