2016-06-19 9 views
7

std::unique_ptrには2つのテンプレートパラメータがあり、2番目のテンプレートパラメータは使用するデリータです。この事実のおかげで、1は、簡単にカスタム削除手段を必要とするタイプ、(例えばSDL_Textureを)にunique_ptrは、次のようにすることができますエイリアス:SDL2PtrDeleterはファンクタをあるstd :: shared_ptrを作成またはリセットするたびにdeleterを指定する必要性を避けるにはどうすればよいですか?

using SDL_TexturePtr = unique_ptr<SDL_Texture, SDL2PtrDeleter>; 

...デリータとして使用されます。

は、このエイリアスを考えると、プログラマは、思いやりのある、あるいはカスタム削除手段については知らなくてもSDL_TexturePtrを構築し、リセットすることができます:

SDL_TexturePtr ptexture(SDL_CreateTexture(/*args*/)); 

//... 

ptexture.reset(SDL_CreateTexture(/*args*/)); 

std::shared_ptr、一方で、テンプレートパラメータを持っていません、型の一部としてdeleterを指定することができるので、以下は違法です。

// error: wrong number of template arguments (2, should be 1) 
using SDL_TextureSharedPtr = shared_ptr<SDL_Texture, SDL2PtrDeleter>; 

したがって、最も適切なのは型

using SDL_TextureSharedPtr = shared_ptr<SDL_Texture>; 

しかし、これは、ユーザが知っている必要がありますので、使用し、それに彼らが構築するたびに指定するか、とにかくSDL_TextureSharedPtrをリセットするには、明示的デリータ機能をshared_ptr<SDL_Texture>を使用しての上にいくつかの利点があります:エイリアスです

SDL_TextureSharedPtr ptexture(SDL_CreateTexture(/*args*/), SDL_DestroyTexture); 

//... 

ptexture.reset(SDL_CreateTexture(/*args*/), SDL_DestroyTexture); 

上の例からわかるように、ユーザーはSDL_Texture(つまりSDL_DestroyTexture())を削除し、そのたびにポインタを渡す正しい関数を知る必要があります。これは不便であるばかりでなく、プログラマが間違った関数をdeleterとして指定することによってバグを導入する可能性が小さくなります。


私はどういうわけか、共有ポインタそのものの型でデリターをカプセル化したいと思っています。 shared_ptrのインタフェースを複製するだろうstd::shared_ptr<T>を、ラップ、クラスを作成します

  1. :方法がないので、私の知る限り、単にタイプの別名を使用することによって、これを達成するために、私は3つのオプションを検討してきました独自のテンプレートパラメータを介してDeleter Functorを指定することができます。このラッパーは、コンストラクターまたはreset()メソッドを基にしたstd::shared_ptr<T>のメソッドを独自のコンストラクターまたはreset()メソッドから呼び出すときに、そのdeleterインスタンスのoperator()へのポインターをそれぞれ提供します。欠点は、もちろん、かなり大きい、std::shared_ptrのインターフェイスは、このラッピングクラスWETで複製する必要があるということです。

  2. std::shared_ptr<T>のサブクラスを作成します。これにより、独自のテンプレートパラメータを使用してDeleter Functorを指定できます。これは、publicの継承を仮定すると、shared_ptrのインタフェースを複製する必要性を避けるのに役立ちますが、独自のワームの缶を開くことになります。 std::shared_ptrfinalではありませんが、非仮想的なデストラクタを持っているので、サブクラス化するようには設計されていないようです(この例では問題はありません)。さらに悪いことに、reset()メソッドはshared_ptrで仮想メソッドではないため、無効にすることはできません。不正使用の扉を開きます。public継承では、ユーザはサブクラスのインスタンスへの参照をいくつかのAPIに渡し、 std::shared_ptr<T>&の実装でreset()が呼び出され、私たちのメソッドを完全に回避します。非公開継承では、オプション#1と同じようになります。上記のオプションの両方について

、最終的に、SDL_TextureSharedPtrMySharedPtr<T, Deleter>が私たちの(サブ)クラスであると仮定すると、以下のように表すことができる:

using SDL_TextureSharedPtr = MySharedPtr<SDL_Texture, SDL2PtrDeleter>; 
  • 3番目のオプションはここにあって、それには専門のstd::default_delete<T>が含まれていました。これは、std::shared_ptr<T>std::default_delete<T>を使用しているという誤った仮定に基づいています。具体的には、除外者が明示的に指定されていない場合はunique_ptrのようになります。これはではなく、の場合です。これを指摘するために@DieterLückingに感謝します!

  • は、これらのオプションと上記の理由を考えると、ここで私の質問です。

    インスタンスが作成されるたびにstd::shared_ptr<T>のデリータを指定しなくても簡単な方法がありませんでしたか?reset()

    もしそうでない場合は、リストに載っているオプションが正しいと思いますか?これらのオプションの1つを別のものよりも優先させる他の客観的な理由はありますか?

    +1

    クラス自体をラップしてRAIIに準拠させ、 'shared_ptr'の最初のテンプレート引数を調整するのはなぜですか? – Columbo

    +0

    私はあなたが本当にカスタムディレクターを必要とする理由はまだ見ていません。 'SDL_DestroyTexture(ptr);'をデストラクタに置かないのはデフォルトのデリゲータと同じですか? – lorro

    +0

    @lorro:それはテクスチャのためだけに機能します。目標はどんなタイプのTでも動作することです。 – Cornstalks

    答えて

    4
    using SDL_TexturePtr = unique_ptr<SDL_Texture, SDL2PtrDeleter>; 
    

    、プログラマは、思いやりのある、あるいはカスタム削除手段については知らなくてもSDL_TexturePtrを構築し、リセットすることができます:

    まあ、それはオーバー簡素化(しばしば致命的な)です。むしろ、デフォルトで構築されたデリータが構築に適している場合、それぞれのデリータの現在の値は手動で変更する必要がないので、リセットポインタに適している。

    shared_ptrのラップまたは拡張の欠点については正しいですが、新しいインスタンスメソッドを追加することができます。
    共用を最小限に抑える必要があります。これは、既存のパブリック・インターフェースを書くのに必要以上に多くの機能を必要としないので、自由な機能を好むことを意味します。

    デリータを指定しないと、std::default_deleteが使用されます(残念ながらそうではありません)。タイプごとに1つのデリターのみが必要です。そうしないと標準的なdelete-expressionが使用できません)、第3の選択肢はあなたが選ぶことができる最高のものです。したがって

    、別のオプション: は(おそらく複合体)の建設やカスタムデリータ抽象化にコンストラクタ関数を使用してください。
    この方法では1度だけ書くことができ、autoのリベラルな使用はさらに頭痛を軽減することができます。

    +2

    3番目のオプションはオプションではありません.std :: default_deleteはstd :: shared_ptrには適用されません。ただし、コンストラクタにDeleterとして明示的に渡されない限り –

    +1

    @DieterLücking:ちょうどそれを読んで、そしてありがとう、あなたは残念なことに正しいです。今修正されました。 – Deduplicator

    2

    あなたは、オプションとして、変更されていない機能を公開するために多くのusingディレクティブを含むプライベート継承を含めませんでした。

    プライベートコピーを使用して共有ptrを書き換えるよりも簡単ですが、暴露の恐れなしにカスタムresetを書き込むことができます。

    また、shared ptrには、一意のptrから変換されたctorがあります。ファクトリ関数が一意のptrsを作成する場合は、必要に応じて共有ptrsに割り当てることができますが、正しいdeleterが使用されます。コード内の未処理のポインタを取り除くと、リセットの問題はなくなります。このエイリアスを考える

    +1

    これは答えよりもコメントのようです。 –

    +0

    @nicolそれは、 "インスタンスが構築またはリセット()されるたびに、std :: shared_ptr のDeleterを指定する必要を避ける簡単な方法がありませんでしたか?これは彼が逃したオプションで、OPのオプションより簡単です。 – Yakk

    +0

    @ヤクありがとう、ここにupvoteです。正直言って、私は私が私の親のメソッドを公開するために使われていることは決してなかったので、私はあなたに "多義的な機能を公開するための指示"を使用することを意味していました(私は私の '私的継承非常にまれに一般的に)。この[SO答え](http://stackoverflow.com/a/2351632/1051764)は私のためにそれをクリアしました。私のように、暗闇の中にいるかもしれない他人の利益のために、あなたのアイデアを親メンバーの「使用」で説明する簡単なコードサンプルや、言及した答えへのリンクを含めるようにお願いします。 – TerraPass

    1

    あなたはあまりにも型自体にデリターを置くことにつきました。代わりにshared_ptrインスタンスから来てどこに焦点を当てます。

    この問題に対する最も効果的な解決策は、このシステムのshared_ptr sが導入なっている場所を適切に一元化することです。それらを生成する単一の関数が存在しなければなりません。適切なデリゲーターを添付する責任があります。

    明らかに、このようなシステムは保証を提供しません。しかし、単にshared_ptr::resetを使用することは決してありません(実際にはそうする理由はほとんどありません)。直接作成することは決してありません(コピー/移動は問題ありませんが、他のコンストラクタは安全ではありません)。 shared_ptrを新しいインスタンスに再割り当てする必要がある場合は、operator=を使用してください。それが何のためだ。

    最終的には、これはmake_sharedのリベラル利用したコードベースと違いはありません。

    1

    あなたが使用することができるはずveneer

    で一般化することができる
    // A shared_ptr which will use SDL2PtrDeleter **by default**: 
    class SharedTexure : public std::shared_ptr<SDL_Texture> { 
    public: 
        constexpr SharedTexure() : std::shared_ptr<SDL_Texture>() {} 
        constexpr SharedTexure(std::nullptr_t) : std::shared_ptr<SDL_Texture>() {} 
        explicit SharedTexure(SDL_Texture* texture) : 
        std::shared_ptr<SDL_Texture>(texture, SDL2PtrDeleter()) {} 
        SharedTexture(std::shared_ptr<SDL_Texture> texture) : 
        std::shared_ptr<SDL_Texture>(std::move(texture)) {} 
    }; 
    

    template<class T, class D> 
    class shared_ptr : public std::shared_ptr<T> { 
    public: 
        using std::shared_ptr<T>::shared_ptr; 
        template<class U> 
        explicit shared_ptr(U* ptr) : 
        std::shared_ptr<T>(ptr, D()) {} 
    }; 
    

    template<class T, class D> 
    class shared_ptr : public std::shared_ptr<T> { 
    public: 
        constexpr shared_ptr() : std::shared_ptr<T>() {} 
        constexpr shared_ptr(std::nullptr_t) : std::shared_ptr<T>() {} 
        template<class U> 
        explicit shared_ptr(U* ptr) : 
        std::shared_ptr<T>(ptr, D()) {} 
        template<class U> 
        shared_ptr(std::shared_ptr<U> ptr) : 
        std::shared_ptr<T>(std::move(ptr)) {} 
    }; 
    
    using SharedTexure = shared_ptr<SDL_Texture, SDL2PtrDeleter>; 
    

    あなたはコンストラクタを継承することができるはずですそれはsubclであるように設計されたようではありませんそれは非仮想デストラクタ

    を持っているので 、assed referenced paperは、このユースケースのために安全であることを意味します。しかし、標準からの適切な参照を持つことは興味深いでしょう。

    +0

    リンクをありがとう。これはいいですが、もし私が何かを見逃していなければ、これは 'reset()'に関する部分に対処していないようです。 – TerraPass

    関連する問題