62

動作を構成しようとしているクラスがあります。

template<int ModeT, bool IsAsync, bool IsReentrant> ServerTraits;

その後、サーバーオブジェクト自体があります。

template<typename TraitsT>
class Server {…};

私の質問は、上記の私の使用法についてですが、私の名前は間違っていますか?テンプレート化されたパラメーターは、実際には特性ではなくポリシーですか?

テンプレート化された引数は、いつポリシーに対して特性になりますか?

4

4 に答える 4

87

ポリシー

ポリシーは、通常は継承を通じて動作を親クラスに注入するためのクラス(またはクラステンプレート)です。親インターフェイスを直交する(独立した)ディメンションに分解することにより、ポリシークラスはより複雑なインターフェイスのビルディングブロックを形成します。よく見られるパターンは、ライブラリが提供するデフォルトを使用して、ユーザー定義可能なテンプレート(またはtemplate-template)パラメーターとしてポリシーを提供することです。標準ライブラリの例は、すべてのSTLコンテナのポリシーテンプレートパラメータであるアロケータです。

template<class T, class Allocator = std::allocator<T>> class vector;

ここで、Allocatorテンプレートパラメータ(それ自体もクラステンプレートです!)は、メモリの割り当てと割り当て解除のポリシーを親クラスに挿入しますstd::vector。ユーザーがアロケータを指定しない場合は、デフォルトstd::allocator<T>が使用されます。

テンプレートベースのポリポンフィズムで一般的であるように、ポリシークラスのインターフェイス要件は、明示的で構文的(仮想メンバー関数の定義に基づく)ではなく、暗黙的でセマンティック(有効な式に基づく)です。

最近の順序付けされていない連想コンテナには、複数のポリシーがあることに注意してください。通常のAllocatorテンプレートパラメータに加えて、Hashデフォルトでstd::hash<Key>関数オブジェクトになるポリシーも採用しています。これにより、順序付けされていないコンテナーのユーザーは、複数の直交する次元(メモリー割り当てとハッシュ)に沿ってコンテナーを構成できます。

特性

トレイトは、ジェネリック型からプロパティを抽出するためのクラステンプレートです。特性には、単一値の特性と複数値の特性の2種類があります。単一値の特性の例は、ヘッダーからのものです。<type_traits>

template< class T >
struct is_integral
{
    static const bool value /* = true if T is integral, false otherwise */;
    typedef std::integral_constant<bool, value> type;
};

単一値の特性は、型の条件に基づいて関数テンプレートをオーバーロードするために、テンプレートメタプログラミングおよびSFINAEトリックでよく使用されます。

複数値の特性の例は、それぞれヘッダー<iterator>とのiterator_traitsとallocator_traits<memory>です。トレイトはクラステンプレートであるため、特殊化できます。の専門化の例の下iterator_traitsT*

template<T>
struct iterator_traits<T*>
{
    using difference_type   = std::ptrdiff_t;
    using value_type        = T;
    using pointer           = T*;
    using reference         = T&;
    using iterator_category = std::random_access_iterator_tag;
};

この式std::iterator_traits<T>::value_typeにより、本格的なイテレータクラスのジェネリックコードをrawポインタでも使用できるようになります(rawポインタにはメンバーがないためvalue_type)。

ポリシーと特性の間の相互作用

独自の汎用ライブラリを作成するときは、ユーザーが独自のクラステンプレートを特殊化する方法を考えることが重要です。ただし、動作を抽出するのではなく、特性の特殊化を使用して注入することにより、ユーザーが単一定義規則の犠牲にならないように注意する必要があります。アンドレイ・アレキサンドレスクによるこの古い投稿を言い換えると

基本的な問題は、トレイトの特殊なバージョンが表示されないコードでもコンパイルされ、リンクされる可能性が高く、場合によっては実行される可能性があることです。これは、明示的な特殊化がない場合、特殊化されていないテンプレートが起動し、特殊なケースでも機能する一般的な動作を実装する可能性があるためです。したがって、アプリケーション内のすべてのコードが同じ特性の定義を認識していない場合、ODRに違反します。

C ++ 11は、すべてのSTLコンテナーがポリシーstd::allocator_traitsからのみプロパティを抽出できるようにすることで、これらの落とし穴を回避します。ユーザーが必要なポリシーメンバーの一部を提供しない、または提供するのを忘れた場合、トレイトクラスはステップインして、欠落しているメンバーのデフォルト値を提供できます。それ自体を特殊化することはできないため、ユーザーはコンテナーのメモリ割り当てをカスタマイズするために、常に完全に定義されたアロケーターポリシーを渡す必要があり、サイレントODR違反が発生することはありません。Allocatorstd::allocator_traits<Allocator>allocator_traits

ライブラリライターとして、(STLのようにiterator_traits<T*>)トレイトクラステンプレートを特殊化することはできますが、すべてのユーザー定義の特殊化をポリシークラスを介して、特殊化された動作を抽出できる複数値の特性に渡すことをお勧めします( STLのようにallocator_traits<A>)。

更新:トレイトクラスのユーザー定義の特殊化のODR問題は、主にトレイトがグローバルクラステンプレートとして使用される場合に発生し、将来のすべてのユーザーが他のすべてのユーザー定義の特殊化を表示することを保証できません。ポリシーはローカルテンプレートパラメータであり、関連するすべての定義が含まれているため、他のコードに干渉することなくユーザー定義を行うことができます。タイプと定数のみを含み、動作関数を含まないローカルテンプレートパラメータは、「トレイト」と呼ばれる場合がありますが、std::iterator_traitsやなどの他のコードには表示されませんstd::allocator_traits

于 2013-02-06T07:47:09.893 に答える
25

アンドレイ・アレキサンドレスクのこの本で、あなたの質問に対する最良の答えが見つかると思います。ここでは、簡単な概要を説明します。うまくいけば、それが役立つでしょう。


トレイトクラスは、通常、タイプを他のタイプまたは定数値に関連付けてそれらのタイプの特性を提供するメタ関数となることを目的としたクラスです。つまり、型のプロパティをモデル化する方法です。このメカニズムは通常、テンプレートとテンプレートの特殊化を利用して、関連付けを定義します。

template<typename T>
struct my_trait
{
    typedef T& reference_type;
    static const bool isReference = false;
    // ... (possibly more properties here)
};

template<>
struct my_trait<T&>
{
    typedef T& reference_type;
    static const bool isReference = true;
    // ... (possibly more properties here)
};

上記のトレイトメタ関数my_trait<>は、参照型T&と定数ブール値を、それ自体が参照ではないfalseすべての型に関連付けます。一方、参照型と定数ブール値を参照であるすべての型に関連付けます。TT&trueT

たとえば、次のようになります。

int  -> reference_type = int&
        isReference = false

int& -> reference_type = int&
        isReference = true

コードでは、上記を次のようにアサートできます(以下の4行すべてがコンパイルされます。つまり、最初の引数で表現された条件static_assert()が満たされます)。

static_assert(!(my_trait<int>::isReference), "Error!");
static_assert(  my_trait<int&>::isReference, "Error!");
static_assert(
    std::is_same<typename my_trait<int>::reference_type, int&>::value, 
    "Error!"
     );
static_assert(
    std::is_same<typename my_trait<int&>::reference_type, int&>::value, 
    "Err!"
    );

ここでは、標準テンプレートを使用したことがわかります。これは、それ自体が1つではなく2つの型引数std::is_same<>受け入れるメタ関数です。ここでは、物事が任意に複雑になる可能性があります。

std::is_same<>はヘッダーの一部ですが、クラステンプレートがメタ述語として機能する(つまり、 1つのテンプレートパラメーターtype_traitsを受け入れる)場合にのみ、クラステンプレートを型特性クラスと見なす人もいます。しかし、私の知る限り、用語は明確に定義されていません。

C ++標準ライブラリでの特性クラスの使用例については、入出力ライブラリと文字列ライブラリがどのように設計されているかを見てください。


ポリシーは少し異なります(実際にはかなり異なります)。これは通常、いくつかの異なる方法で実現される可能性のある特定の操作に関する別のジェネリッククラスの動作を指定するクラスを意味します(したがって、その実装はポリシークラスに任されます)。

たとえば、ジェネリックスマートポインタクラスは、参照カウントの処理方法を決定するためのテンプレートパラメータとしてポリシーを受け入れるテンプレートクラスとして設計できます。これは単なる架空の、過度に単純化された、説明的な例なので、抽象化してみてください。この具体的なコードから、メカニズムに焦点を当てます。

これにより、スマートポインターの設計者は、参照カウンターの変更をスレッドセーフな方法で実行するかどうかについて、ハードコードされたコミットメントを行うことができなくなります。

template<typename T, typename P>
class smart_ptr : protected P
{
public:
    // ... 
    smart_ptr(smart_ptr const& sp)
        :
        p(sp.p),
        refcount(sp.refcount)
    {
        P::add_ref(refcount);
    }
    // ...
private:
    T* p;
    int* refcount;
};

マルチスレッドのコンテキストでは、クライアントは、参照カウンターのスレッドセーフなインクリメントとデクリメントを実現するポリシーでスマートポインターテンプレートのインスタンス化を使用できます(ここではWindowsプラットフォームを想定)。

class mt_refcount_policy
{
protected:
    add_ref(int* refcount) { ::InterlockedIncrement(refcount); }
    release(int* refcount) { ::InterlockedDecrement(refcount); }
};

template<typename T>
using my_smart_ptr = smart_ptr<T, mt_refcount_policy>;

一方、シングルスレッド環境では、クライアントは、カウンターの値を単純に増減するポリシークラスを使用してスマートポインターテンプレートをインスタンス化できます。

class st_refcount_policy
{
protected:
    add_ref(int* refcount) { (*refcount)++; }
    release(int* refcount) { (*refcount)--; }
};

template<typename T>
using my_smart_ptr = smart_ptr<T, st_refcount_policy>;

このように、ライブラリ設計者は、パフォーマンスと安全性の間で最良の妥協点を提供できる柔軟なソリューションを提供しました(「使用しないものにお金を払わない」)。

于 2013-02-05T23:20:05.997 に答える
3

ModeT、IsReentrant、およびIsAsyncを使用してサーバーの動作を制御している場合、それはポリシーです。

または、サーバーの特性を別のオブジェクトに記述する方法が必要な場合は、次のようにトレイトクラスを定義できます。

template <typename ServerType>
class ServerTraits;

template<>
class ServerTraits<Server>
{
    enum { ModeT = SomeNamespace::MODE_NORMAL };
    static const bool IsReentrant = true;
    static const bool IsAsync = true;
}
于 2013-06-17T23:11:20.833 に答える
1

アレックスチェンバレンのコメントを明確にするためのいくつかの例を次に示します。

トレイトクラスの一般的な例はstd::iterator_traitsです。2つのイテレータを受け取り、値を反復処理し、何らかの方法で結果を累積するメンバー関数を持つテンプレートクラスCがあるとします。蓄積戦略もテンプレートの一部として定義する必要がありますが、それを実現するために特性ではなくポリシーを使用します。

template <typename Iterator, typename AccumulationPolicy>
class C{
    void foo(Iterator begin, Iterator end){
        AccumulationPolicy::Accumulator accumulator;
        for(Iterator i = begin; i != end; ++i){
            std::iterator_traits<Iterator>::value_type value = *i;
            accumulator.add(value);
        }
    }
};

ポリシーはテンプレートクラスに渡されますが、トレイトはテンプレートパラメータから派生します。つまり、あなたが持っているものは、よりポリシーに似ています。特性がより適切であり、ポリシーがより適切である状況があり、多くの場合、どちらの方法でも同じ効果を達成でき、どちらが最も表現力があるかについての議論につながります。

于 2013-02-05T23:12:58.000 に答える