Kelas pembungkus C ++ untuk larik objek buffer OpenGL - hanya konstruktornya

Aug 18 2020

Pertanyaan serupa ditanyakan di sini , dan saya mencoba melakukan hal yang sama. Tapi

  1. Saya mencoba pendekatan yang berbeda dengan mengambil dari std::array, dan
  2. Ini adalah pertanyaan yang sangat terfokus tentang konstruktor saja.

Ini milik saya 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

Beberapa pertanyaan khusus yang saya miliki tentang kode / pendekatan ini adalah, jika berlaku,

  1. Kesalahan menangani keputusan desain: haruskah konstruktor melempar, atau haruskah saya menyerahkan pemeriksaan kesalahan, jika diinginkan, kepada pemanggil?
  2. Keputusan desain: apakah menggunakan template di sini adalah ide yang bagus? Apakah ini akan menyebabkan kode yang bermasalah membengkak jika saya menggunakan banyak ukuran yang berbeda buffer_objects, atau apakah itu efisien? Dapatkah saya mengevaluasinya melalui pembuatan profil?
  3. Apakah memcpypantas untuk menempatkan nilai r dalam status tidak memiliki yang valid, atau dapatkah saya memperlakukan nilai r sebagai sisa yang tidak memiliki?
  4. Apakah saya benar menggunakan konstruktor pemindahan kelas dasar?

Jawaban

5 MartinYork Aug 18 2020 at 04:43

Beberapa pertanyaan khusus yang saya miliki tentang kode / pendekatan ini adalah, jika berlaku,

BAIK.

Kesalahan menangani keputusan desain: haruskah konstruktor melempar, atau haruskah saya menyerahkan pemeriksaan kesalahan, jika diinginkan, kepada pemanggil?

Entah objek tersebut diinisialisasi dengan benar dan siap digunakan atau harus dibuang. Inisialisasi dua tahap (buat lalu periksa) adalah ide yang buruk karena membiarkan pengguna terbuka untuk melakukan hal yang benar. Anda harus memastikan objek Anda tidak dapat disalahgunakan tidak bergantung pada pengguna untuk tidak menyalahgunakan Anda.

Keputusan desain: apakah menggunakan template di sini adalah ide yang bagus?

Jika Anda akan menggunakan std::arrayAnda tidak benar-benar punya pilihan.

Apakah ini akan menyebabkan kode yang bermasalah membengkak jika saya menggunakan banyak buffer_objects dengan ukuran berbeda, atau apakah itu efisien? Dapatkah saya mengevaluasinya melalui pembuatan profil?

Hal ini kemungkinan besar akan mengarah pada metode yang dikompilasi untuk jenis yang berbeda. Tetapi ini bergantung pada kompilator dan beberapa kompilator modern dapat mengoptimalkannya. Tapi apa yang kamu bandingkan juga?

Apakah memcpys layak untuk menempatkan nilai r dalam status tidak memiliki yang valid, atau dapatkah saya memperlakukan nilai r sebagai sisa yang tidak memiliki?

Saya pikir Anda perlu membatalkan srcnilainya. Jika tidak, destruktor akan melepaskan nama-nama itu. Alternatifnya, Anda dapat menyimpan lebih banyak status tentang apakah objek tersebut valid dan membuat panggilan untuk glDeleteBuffers()bersyarat pada objek yang berada dalam status valid.

Tapi saya juga berpikir ada bug di sini. Anda baru saja menyalin nilainya. Tetapi Anda tidak melepaskan nilai di dstsamping sebelum salinan sehingga Anda kehilangan nama yang disimpan di sana.

Ingat bahwa std :: array tidak memiliki perpindahannya sendiri (karena datanya bersifat lokal ke objek yang tidak dialokasikan secara dinamis). Ini memindahkan objek yang mendasarinya di antara kontainer.

Apakah saya benar menggunakan konstruktor pemindahan kelas dasar?

Ya sepertinya ligit.

Saran.

Saya akan membuat std::arrayanggota agak mewarisi darinya.

Saya akan melakukan:

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