คลาส Wrapper 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มูลค่ามันจะนำ rvalues ในรัฐที่ไม่ได้เป็นเจ้าของที่ถูกต้องหรือผมสามารถรักษา rvalues เป็นของเหลือที่ไม่ได้เป็นเจ้าของ?
  4. ฉันใช้ตัวสร้างการย้ายของคลาสพื้นฐานอย่างถูกต้องหรือไม่

คำตอบ

5 MartinYork Aug 18 2020 at 04:43

คำถามเฉพาะบางอย่างที่ฉันมีเกี่ยวกับรหัส / แนวทางนี้คือถ้ามี

ตกลง.

ข้อผิดพลาดในการจัดการกับการตัดสินใจในการออกแบบ: ผู้สร้างควรโยนทิ้งหรือฉันควรปล่อยให้ผู้โทรตรวจสอบข้อผิดพลาดหากต้องการ

วัตถุนั้นเริ่มต้นอย่างถูกต้องและพร้อมใช้งานหรือควรโยนทิ้ง การเริ่มต้นสองขั้นตอน (สร้างแล้วตรวจสอบ) เป็นความคิดที่ไม่ดีเนื่องจากปล่อยให้ผู้ใช้ทำสิ่งที่ถูกต้อง คุณควรตรวจสอบให้แน่ใจว่าวัตถุของคุณไม่สามารถถูกทำร้ายได้ไม่ต้องพึ่งพาผู้ใช้ที่จะไม่ละเมิดคุณ

การตัดสินใจในการออกแบบ: ควรใช้เทมเพลตที่นี่หรือไม่

หากคุณจะใช้std::arrayคุณไม่มีทางเลือกจริงๆ

สิ่งนี้จะนำไปสู่การขยายโค้ดที่มีปัญหาหรือไม่หากฉันใช้ buffer_objects ขนาดต่างๆจำนวนมากหรือมีประสิทธิภาพหรือไม่ ฉันสามารถประเมินสิ่งนี้ผ่านการทำโปรไฟล์ได้หรือไม่

สิ่งนี้อาจนำไปสู่การรวบรวมวิธีการสำหรับประเภทต่างๆ แต่นี่ขึ้นอยู่กับคอมไพเลอร์และคอมไพเลอร์สมัยใหม่บางตัวสามารถปรับให้เหมาะสมได้ แต่คุณเปรียบเทียบมันด้วยอะไร?

memcpys คุ้มค่าหรือไม่ที่จะทำให้ rvalues ​​อยู่ในสถานะที่ไม่เป็นเจ้าของที่ถูกต้องหรือฉันสามารถถือว่า rvalues ​​เป็นของเหลือที่ไม่ได้เป็นเจ้าของได้หรือไม่

ฉันคิดว่าคุณต้องลบค่าออกsrcไป มิฉะนั้นผู้ทำลายจะปล่อยชื่อ หรือคุณสามารถจัดเก็บสถานะเพิ่มเติมเกี่ยวกับว่าอ็อบเจ็กต์ถูกต้องหรือไม่และทำการเรียกglDeleteBuffers()เงื่อนไขบนอ็อบเจ็กต์ที่อยู่ในสถานะที่ถูกต้อง

แต่ฉันก็คิดว่ามีจุดบกพร่องที่นี่ คุณเพิ่งคัดลอกค่าต่างๆ แต่คุณไม่ได้ปล่อยค่าที่dstด้านข้างก่อนสำเนาดังนั้นคุณจึงสูญเสียชื่อที่เก็บไว้ที่นั่น

โปรดจำไว้ว่าอาร์เรย์ std :: ไม่มีการย้ายของตัวเอง (เนื่องจากข้อมูลอยู่ในพื้นที่ของวัตถุที่ไม่ได้รับการจัดสรรแบบไดนามิก) มันย้ายวัตถุที่อยู่ข้างใต้ระหว่างคอนเทนเนอร์

ฉันใช้ตัวสร้างการย้ายของคลาสพื้นฐานอย่างถูกต้องหรือไม่

ใช่ดูเหมือนว่า ligit

ข้อเสนอแนะ.

ฉันจะให้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;
    }
}