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. あるmemcpyそれが有効な非所有状態に右辺値を置くための価値、またはIは非所有残り物として右辺値を扱うことができますか?
  4. 基本クラスのmoveコンストラクターを適切に使用していますか?

回答

5 MartinYork Aug 18 2020 at 04:43

このコード/アプローチについて私が持っているいくつかの具体的な質問は、該当する場合、

OK。

エラー処理の設計上の決定:コンストラクターはスローする必要がありますか、それとも必要に応じて呼び出し元にエラーチェックを任せる必要がありますか?

オブジェクトが正しく初期化されて使用できる状態になっているか、スローする必要があります。2段階の初期化(構築してからチェック)は、ユーザーが正しいことを実行できるようにするため、お勧めできません。オブジェクトが悪用されないようにする必要があります。悪用しないようにユーザーに依存しないでください。

設計上の決定:ここでテンプレートを使用するのは良い考えですか?

あなたが使用しようとしているなら、あなたはstd::array本当に選択の余地がありません。

サイズの異なるbuffer_objectsを多数使用している場合、これは問題のあるコードの膨張につながりますか、それとも効率的ですか?プロファイリングを通じてこれを評価できますか?

これにより、さまざまなタイプのメソッドがコンパイルされる可能性があります。しかし、これはコンパイラに依存しており、一部の最新のコンパイラはこれを最適化できます。しかし、あなたもそれを何と比較していますか?

memcpysは、右辺値を有効な非所有状態にする価値がありますか、それとも右辺値を非所有の残り物として扱うことができますか?

src値をnullにする必要があると思います。それ以外の場合、デストラクタは名前を解放します。または、オブジェクトが有効かどうかについてより多くの状態を格納し、オブジェクトが有効なglDeleteBuffers()状態にあることを条件としてを呼び出すことができます。

しかし、ここにもバグがあると思います。値をコピーしました。しかしdst、コピーの前に側面の値を解放しなかったため、そこに保存されている名前が失われました。

std :: arrayには独自の移動がないことに注意してください(データは動的に割り当てられていないオブジェクトに対してローカルであるため)。基になるオブジェクトをコンテナ間で移動します。

基本クラスのmoveコンストラクターを適切に使用していますか?

はい、それは合法のようです。

提案。

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