web-dev-qa-db-ja.com

ゼロから参照カウントを実装するか、リソースにshared_ptrを使用していますか?

私が書いているOpenGLアプリケーションでは、OpenGLシェーダーハンドルをラップする単純なシェーダークラスが必要です。最終的には、このシェーダークラスがc ++のshared_ptrと非常によく似た動作をするようにします(つまり、参照カウントを保持し、参照が残っていない場合はリソースを解放します)。この参照カウントを最初から実装するのは比較的簡単ですが、代わりにstd::shared_ptrをカスタムの削除機能と共に使用してリソースを解放する方が良い設計の選択と考えられるかどうか疑問に思いました。

私の疑問の主な原因は、(私が知る限り)OpenGLシェーダープログラムハンドルの作成が実際にはヒープメモリを必要としない(shared_ptrが処理する)ために、型破りであると考えられるかもしれないという事実です。ヒープメモリの処理方法と非常によく似た方法でこのリソースを処理したいので、ここでも適用できると思います。この質問の目的は、基本的に私が知らないため、これが実際に型破りであるかどうかについて他の人の意見を求めることです。

また、例としてシェーダーを使用しましたが、OpenGLのテクスチャーとバッファーも割り当ておよび解放する必要があるため、同じ質問が当てはまります。

5

_unique_ptr<T, D>_は、実際には、より任意のハンドルのような型を処理できるように特別に設計されています。ここではDがキーであるため、テンプレート名を完全に入力しました。通常unique_ptr<T, D>::get()は_T*_を返します。これがデフォルトですが、これはDによってオーバーライドできます:削除タイプ。

削除タイプにpointerエイリアスがある場合(_D::pointer_は正しい構文です)、unique_ptr<T, D>::get()はそのタイプを返します。これにより、_unique_ptr<GLuint, gl::program_deleter>_のようなものを使用できます。ここで、_gl::program_deleter::pointer_はintタイプです。

_shared_ptr_ これはできませんのため、これらすべてを取り上げます。削除者は実際には_unique_ptr<T, D>_型自体の一部であるため、_unique_ptr_はそれを回避します。対照的に、_shared_ptr_のコンストラクターは削除機能を使用できますが、削除機能が実行できるのはメモリを削除することだけです。

したがって、shared_ptr<GLuint>::get()は常に_GLuint*_を返します。つまり、ある種の共有ハンドルタイプとして_shared_ptr_を使用する場合、そのタイプは何らかの方法で動的に割り当てる必要があります。グローバルヒープを使用していない可能性がありますが、整数を格納して返すこともできません。 _shared_ptr<T>_には常に_T*_が含まれます。

したがって、_GLuint*_の参照カウント機構を使用する場合は、_shared_ptr_ sを管理する必要があります。はい、削除機能を使用してglDeleteProgramまたは必要なものを呼び出すことができますが、_shared_ptr<GLuint>_は_GLuint*_を保存します。

openGLシェーダープログラムハンドルの作成には、実際にはヒープメモリは含まれません。

OK、少しの間、OpenGLオブジェクトを作成することで、ドライバーがヒープにメモリを割り当てたことを忘れないでください。あなたがしなければならないことだけを見てみましょう。

一部のストレージを所有する_shared_ptr_を作成することにより、somethingが割り当てられます。つまり、_shared_ptr_の参照カウントを管理する共有ブロックです。それを回避する方法はありません。したがって、_shared_ptr_の参照カウントインフラストラクチャを使用する場合は、どこかから割り当てることになります。

したがって、これを行う最も慣用的な方法は、GLuintをヒープに割り当て、OpenGLオブジェクトを破棄して整数の割り当てを解除する特別な削除機能を使用することです。それはかわいくなく、それは一種の無駄ですが、それはほとんどひどいことではありません。また、_make_shared_を使用すると、割り当てに関してかなりコンパクトになります。


現在、この割り当てはcheatingで回避できます。あなたはこれを行うことができます:

_GLuint program = glCreateProgram();
shared_ptr<GLuint> sp(reinterpret_cast<GLuint*>(program), ProgramDeleter);
_

したがって、ここでは、整数を取得してポインタ値にキャストし、_shared_ptr_内に格納します。使用する必要がある場合は、キャストを逆にして整数値を回復する必要があります。

ただし、次のコードを自分で判断してください。

_glProgramUniform1i(reinterpret_cast<GLuint>(sp.get()), val);
_

それはあなたが頻繁にやりたいことのように見えますか? read頻繁にやりたいことのように見えますか?他の誰かが何が起こっているのか簡単に理解できるようなものですか?

それだけでなく、ポインタ値isが問題の値であるため、_*sp_を使用して値を取得することはできません。

ああ、そして参照カウント制御ブロックはまだヒープが割り当てられているので、メモリや何かの割り当てを妨げるようなものではありません。

これは慣用的なC++ではありません。

8
Nicol Bolas

これを試してみると、ハンドルクラスの特定のセットを作成し、標準のスマートポインターを使用していました。

ここで完全なソース:

https://github.com/AlexAndDad/dungeon/blob/master/opengl/shader.hpp

サービス/ハンドルイディオムを使用して、ハンドルの動作(共有または一意)と機能の懸念を分離しました。

ここからの抜粋:

/// Resource Service base implementation
/// When a resource_object manages its resources (e.g. copy, move, construction, destruction etc) it will
/// defer to its service object. The service object will be a concrete class derived from this class.
/// The reason for this is that the service can then handle the details around creating and deleting
/// GL handle objects and managing their lifetimes.
/// @tparam Derived is the concrete service class derived from this class
/// @tparam NativeType is the type used to store the underlying GL handle or collection of handles
template<typename Derived, typename NativeType = GLuint>
struct basic_resource_service;

template<class Derived>
struct basic_resource_service<Derived, GLuint> : notstd::stateless_service<Derived>
{
    using native_handle_type = GLuint;
    using implementation_type = native_handle_type;

    /// Determine whether the implementation is empty,i.e. does not represent a GL handle.
    bool empty(implementation_type const &impl) const noexcept
    {
        return not impl;
    }

    void invalidate(implementation_type& impl) const noexcept
    {
        impl = 0;
    }
};

/// A specialisation of basic_resource_service which handles a vector of GL handles
template<class Derived, class DataType>
struct basic_resource_service<Derived, std::vector<DataType>>
{
    using implementation_type = std::vector<DataType>;

    bool empty(implementation_type const &impl) const
    {
        return not impl.empty();
    }

    void invalidate(implementation_type& impl) const
    {
        impl.clear();
    }
};

struct shader_service : basic_resource_service<shader_service, GLuint>
{
    /// Construct a shader identity of a given type
    /// @param type is a gl shader type enum
    /// @return the gl id of a new shader
    ///
    static auto construct(shader_type type) -> implementation_type;

    /// Destroy a gl shader object if not zero
    /// @param impl is a reference to a shader id
    /// @pre impl contains either a valid shader object id or 0
    /// @post impl shall contain 0
    ///
    static auto destroy(implementation_type &impl) -> void;


    static std::size_t log_length(implementation_type const &impl);

    static std::string log(implementation_type const &impl);

    static std::size_t source_length(implementation_type const &impl);

    static std::string source(implementation_type const &impl);
};

struct shader_compilation_failed : std::runtime_error
{
    using std::runtime_error::runtime_error;
};

/// The representation of some kind of shader
struct shader : notstd::unique_handle<shader_service>
{
    shader(shader_type type)
        : notstd::unique_handle<shader_service>(std::make_Tuple(type))
    {
    }

    template<class...Sources>
    shader(shader_type type, Sources &&...sources)
        : shader(type)
    {
        constexpr auto count = sizeof...(sources);

        const GLchar *sz_sources[] =
            {
                detail::to_gl_char(sources)...
            };

        const GLint lengths[] = {
            detail::get_gl_string_length(sources)...
        };

        glShaderSource(native_handle(), count, sz_sources, lengths);
        glCompileShader(native_handle());
        check_errors("shader::shader");
        if( not compiled())
        {
            throw shader_compilation_failed(log());
        }
    }

    /// Return the source code for this shader object, if it has any
    auto source() const -> std::string;

    /// Return the shader type
    auto type() const -> shader_type;

    /// Check whether the shader has compiled
    auto compiled() const -> bool;

    /// Return the log text associated with this shader
    auto log() const -> std::string;
};

struct fragment_shader : shader
{
    template
        <
            class String,
            std::enable_if_t
                <
                    not std::is_base_of<shader, std::decay_t<String>>::value
                > * = nullptr
        >
    fragment_shader(String &&str)
        : shader(shader_type::fragment, std::forward<String>(str))
    {

    }
};
0
Richard Hodges