web-dev-qa-db-ja.com

特性とポリシーの違いは何ですか?

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

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

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

template<typename TraitsT>
class Server {...};

私の質問は、上記の私の使用法に対する私の名前の名前の誤りですか?テンプレート化されたパラメーターは、実際には特性ではなくポリシーですか?

テンプレート化された引数は、いつ特性とポリシーのどちらになりますか?

59
Nathan Doromal

ポリシー

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

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

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

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

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

特性

トレイトは、ジェネリック型からプロパティを抽出するクラステンプレートです。特性には、単一値の特性と複数値の特性の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;
};

単一値の特性は、template-metaprogrammingおよびSFINAEトリックでよく使用され、型条件に基づいて関数テンプレートをオーバーロードします。

複数値の特性の例は、それぞれヘッダー<iterator>および<memory>のiterator_traitsおよびallocator_traitsです。特性はクラステンプレートであるため、特殊化することができます。 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を使用すると、未加工のポインターでも使用可能な本格的な反復子クラスの汎用コードを作成できます(未加工のポインターにはメンバーvalue_typeがないため)。

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

独自の汎用ライブラリを作成するときは、ユーザーが独自のクラステンプレートを特化する方法を考えることが重要です。ただし、動作を抽出するのではなく、特性の特殊化を使用して注入することにより、ユーザーをOne Definition Ruleの犠牲にしないように注意する必要があります。これを言い換えると 古い投稿 Andrei Alexandrescu

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

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

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

[〜#〜] update [〜#〜]:特性クラスのユーザー定義の特殊化のODR問題は、主に特性がグローバルクラステンプレート。将来のすべてのユーザーが他のすべてのユーザー定義の特殊化を参照できることを保証することはできません。ポリシーは、ローカルテンプレートパラメーターであり、関連するすべての定義が含まれているため、他のコードに干渉することなくユーザー定義できます。型と定数のみを含むが、動作的には機能しないローカルテンプレートパラメーターは、「特性」と呼ばれる場合がありますが、std::iterator_traitsstd::allocator_traitsなどの他のコードには表示されません。

84
TemplateRex

Andrei Alexandrescuによるこの本で、あなたの質問に対する最良の答えが見つかると思います。ここでは、簡単な概要を説明します。うまくいけばそれが役立つでしょう。


traits classは、通常、型を他の型または定数値に関連付けてそれらの型の特性を提供するメタ関数であることを意図したクラスです。言い換えれば、それは型のプロパティをモデル化する方法です。このメカニズムは通常、テンプレートとテンプレートの特殊化を利用して関連付けを定義します。

_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を、それ自体がnotではないすべての型Tに関連付けます。一方、参照型_T&_と定数ブール値trueを、参照areであるすべての型Tに関連付けます。

したがって、たとえば:

_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!"
    );
_

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

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

C++標準ライブラリの特性クラスの使用例については、入出力ライブラリと文字列ライブラリの設計方法をご覧ください。


policyは少し異なる(実際にはかなり異なる)ものです。通常は、いくつかの異なる方法で潜在的に実現できる特定の操作に関する別の汎用クラスの動作を指定するクラスを意味します(したがって、その実装はポリシークラスに委ねられます)。

たとえば、ジェネリックスマートポインタークラスは、ref-countingの処理方法を決定するためのテンプレートパラメーターとしてポリシーを受け入れるテンプレートクラスとして設計できます。これは単なる仮説であり、非常に単純で、説明に役立つ例です。この具体的なコードから、メカニズムに焦点を当てます

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

_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>;
_

このように、ライブラリデザイナーは、パフォーマンスと安全性の間の最適な妥協点を提供できる柔軟なソリューションを提供しました(「使用しないものに料金はかかりません」 )。

24
Andy Prowl

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;
}
3
Twisted Oracle

Alex Chamberlainのコメントを明確にするための例をいくつか示します。

特性クラスの一般的な例は、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);
        }
    }
};

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

1
jmetcalfe