Quelle est la différence entre un trait et une politique?

J’ai une classe dont je tente de configurer le comportement.

template ServerTraits; 

Puis, plus tard, mon object serveur lui-même:

 template class Server {...}; 

Ma question est pour mon utilisation ci-dessus est-ce que mon nom est mal nommé? Est-ce que mon paramètre basé sur un modèle est en fait une politique au lieu d’un trait?

Quand un argument basé sur un modèle est-il un trait par rapport à une politique?

Les politiques

Les stratégies sont des classes (ou des modèles de classe) permettant d’ injecter un comportement dans une classe parente, généralement par inheritance. En décomposant une interface parente en dimensions orthogonales (indépendantes), les classes de règles constituent les blocs de construction d’interfaces plus complexes. Un modèle souvent observé consiste à fournir des politiques en tant que parameters de modèle (ou de modèle de modèle) définissables par l’utilisateur avec une valeur par défaut fournie par la bibliothèque. Les Allocators, qui sont des parameters de modèle de politique de tous les conteneurs STL, constituent un exemple de la bibliothèque standard.

 template> class vector; 

Ici, le paramètre de modèle Allocator (qui est également un modèle de classe!) Injecte la politique d’allocation de mémoire et de désallocation dans la classe parente std::vector . Si l’utilisateur ne fournit pas d’allocateur, le std::allocator par défaut est utilisé.

Comme c’est généralement le cas dans le polymporphisme basé sur des modèles, les exigences d’interface sur les classes de règles sont implicites et sémantiques (basées sur des expressions valides) plutôt qu’explicites et syntaxiques (basées sur la définition des fonctions membres virtuelles).

Notez que les conteneurs associatifs non ordonnés les plus récents ont plusieurs stratégies. En plus du paramètre de modèle Allocator habituel, ils adoptent également une stratégie de Hash qui utilise par défaut l’object de fonction std::hash . Cela permet aux utilisateurs de conteneurs non ordonnés de les configurer selon plusieurs dimensions orthogonales (allocation de mémoire et hachage).

Traits

Les traits sont des modèles de classe permettant d’ extraire des propriétés d’un type générique. Il existe deux types de caractères: les caractères à valeur unique et les caractères à valeurs multiples. Des exemples de traits à valeur unique sont ceux de l’en-tête

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

Les traits à valeur unique sont souvent utilisés dans les métaprogrammations de modèles et les astuces SFINAE pour surcharger un modèle de fonction basé sur une condition de type.

Les iterator_traits et allocator_traits des en-têtes et sont des exemples de traits à plusieurs valeurs. Comme les traits sont des modèles de classe, ils peuvent être spécialisés. Ci-dessous un exemple de la spécialisation de iterator_traits pour T*

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

L’expression std::iterator_traits::value_type permet de rendre le code générique pour les classes d’iterators à part entière utilisable même pour les pointeurs bruts (puisque les pointeurs bruts n’ont pas de membre value_type ).

Interaction entre les politiques et les traits

Lorsque vous écrivez vos propres bibliothèques génériques, il est important de réfléchir à la manière dont les utilisateurs peuvent spécialiser vos propres modèles de classe. Il faut cependant faire attention à ne pas laisser les utilisateurs tomber sous la règle de la définition unique en utilisant des spécialisations de traits pour injecter plutôt que pour extraire un comportement. Pour paraphraser cet ancien post de Andrei Alexandrescu

Le problème fondamental est que le code qui ne voit pas la version spécialisée d’un trait comstack encore, est susceptible de se lier, et peut même parfois être exécuté. En effet, en l’absence de spécialisation explicite, le modèle non spécialisé entre en jeu, implémentant probablement un comportement générique qui fonctionne également pour votre cas particulier. Par conséquent, si tout le code dans une application ne voit pas la même définition d’un trait, le ODR est violé.

Le C ++ 11 std::allocator_traits évite ces pièges en imposant que tous les conteneurs STL peuvent uniquement extraire les propriétés de leurs stratégies Allocator via std::allocator_traits . Si les utilisateurs choisissent de ne pas ou d’oublier de fournir certains des membres de stratégie requirejs, la classe de traits peut intervenir et fournir des valeurs par défaut pour ces membres manquants. Comme allocator_traits lui-même ne peut pas être spécialisé, les utilisateurs doivent toujours passer une stratégie d’allocation entièrement définie pour personnaliser l’allocation de mémoire de leurs conteneurs, et aucune violation silencieuse de l’ODR ne peut se produire.

Notez qu’en tant qu’écrivain, vous pouvez toujours spécialiser les modèles de classe de traits (comme le fait la STL dans iterator_traits ), mais il est recommandé de transmettre toutes les spécialisations définies par l’utilisateur à travers des classes de stratégie. extraire le comportement spécialisé (comme le fait la STL dans allocator_traits ).

MISE À JOUR : Les problèmes d’ODR des spécialisations définies par l’utilisateur des classes de traits se produisent principalement lorsque les traits sont utilisés comme modèles de classe globaux et vous ne pouvez pas garantir que tous les futurs utilisateurs verront toutes les autres spécialisations définies par l’utilisateur. Les stratégies sont des parameters de modèle local et contiennent toutes les définitions pertinentes, ce qui leur permet d’être définies par l’utilisateur sans interférence dans d’autres codes. Les parameters de modèle locaux qui ne contiennent que des types et des constantes, mais aucune fonction comportementale, pourraient encore être appelés “traits”, mais ils ne seraient pas visibles par d’autres codes tels que std::iterator_traits et std::allocator_traits .

Je pense que vous trouverez la meilleure réponse possible à votre question dans ce livre d’Andrei Alexandrescu . Ici, je vais essayer de donner un bref aperçu. J’espère que cela aidera.


Une classe de traits est une classe qui est généralement destinée à être une méta-fonction associant des types à d’autres types ou à des valeurs constantes pour fournir une caractérisation de ces types. En d’autres termes, c’est un moyen de modéliser les propriétés des types . Le mécanisme exploite normalement les modèles et la spécialisation de modèle pour définir l’association:

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

Le trait metafunction my_trait<> ci-dessus associe le type de référence T& et la valeur booléenne constante false à tous les types T qui ne sont pas eux-mêmes des références; d’autre part, il associe le type de référence T& et la valeur booléenne constante à tous les types T qui sont des références.

Donc par exemple:

 int -> reference_type = int& isReference = false int& -> reference_type = int& isReference = true 

Dans le code, nous pourrions affirmer ce qui précède comme suit (toutes les quatre lignes ci-dessous seront compilées, ce qui signifie que la condition exprimée dans le premier argument de static_assert() est satisfaite):

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

Ici, vous pouvez voir que j’ai utilisé le modèle standard std::is_same<> , qui est lui-même une méta-fonction qui accepte deux arguments de type plutôt qu’un. Les choses peuvent être compliquées arbitrairement ici.

Bien que std::is_same<> fasse partie de l’en type_traits tête type_traits , certains considèrent qu’un modèle de classe est une classe de caractères uniquement s’il agit comme un méta-prédicat (acceptant ainsi un paramètre de modèle). À ma connaissance, toutefois, la terminologie n’est pas clairement définie.

Pour un exemple d’utilisation d’une classe de traits dans la bibliothèque standard C ++, regardez comment sont conçues la bibliothèque d’entrées / sorties et la bibliothèque de chaînes.


Une politique est quelque chose de légèrement différent (en fait, assez différent). Il est normalement censé être une classe qui spécifie le comportement d’une autre classe générique en ce qui concerne certaines opérations pouvant être réalisées de différentes manières (et dont l’implémentation est donc laissée à la classe de stratégie).

Par exemple, une classe de pointeurs intelligents génériques peut être conçue comme une classe de modèles qui accepte une stratégie en tant que paramètre de modèle pour décider comment gérer le comptage de références – il ne s’agit que d’un exemple hypothétique, trop simpliste et illustratif. de ce code concret et se concentrer sur le mécanisme .

Cela permettrait au concepteur du pointeur intelligent de ne prendre aucun engagement codé en dur pour déterminer si les modifications du compteur de référence doivent être effectuées de manière thread-safe:

 template 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; }; 

Dans un contexte multithread, un client peut utiliser une instanciation du modèle de pointeur intelligent avec une stratégie qui réalise des incréments et des décréments sans risque de thread du compteur de référence (plate-forme Windows supposée ici):

 class mt_refcount_policy { protected: add_ref(int* refcount) { ::InterlockedIncrement(refcount); } release(int* refcount) { ::InterlockedDecrement(refcount); } }; template using my_smart_ptr = smart_ptr; 

En revanche, dans un environnement mono-thread, un client peut instancier le modèle de pointeur intelligent avec une classe de stratégie qui augmente et diminue simplement la valeur du compteur:

 class st_refcount_policy { protected: add_ref(int* refcount) { (*refcount)++; } release(int* refcount) { (*refcount)--; } }; template using my_smart_ptr = smart_ptr; 

De cette façon, le concepteur de la bibliothèque a fourni une solution flexible capable d’offrir le meilleur compromis entre performance et sécurité ( “Vous ne payez pas pour ce que vous n’utilisez pas” ).

Si vous utilisez ModeT, IsReentrant et IsAsync pour contrôler le comportement du serveur, il s’agit d’une stratégie.

Sinon, si vous souhaitez décrire les caractéristiques du serveur à un autre object, vous pouvez définir une classe de traits comme suit:

 template  class ServerTraits; template<> class ServerTraits { enum { ModeT = SomeNamespace::MODE_NORMAL }; static const bool IsReentrant = true; static const bool IsAsync = true; } 

Voici quelques exemples pour clarifier le commentaire d’Alex Chamberlain:

Un exemple courant de classe de trait est std :: iterator_traits. Disons que nous avons une classe de modèle C avec une fonction membre qui prend deux iterators, itère les valeurs et accumule le résultat d’une manière ou d’une autre. Nous voulons aussi que la stratégie d’accumulation fasse partie du modèle, mais nous utiliserons une politique plutôt qu’un trait pour y parvenir.

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

La politique est transmise à notre classe de modèle, tandis que le trait est dérivé du paramètre de modèle. Donc, ce que vous avez ressemble plus à une politique. Il y a des situations où les traits sont plus appropriés, et où les politiques sont plus appropriées, et souvent le même effet peut être obtenu avec l’une ou l’autre méthode menant à un débat sur ce qui est le plus expressif.