Hello à tous !!
Est-ce quelqu'un pourrait m'expliquer la notion des traits ? Je trouve ça très abstraits et j'aimerais savoir dans quel situation on s'en sert.
Mercii !!
Hello à tous !!
Est-ce quelqu'un pourrait m'expliquer la notion des traits ? Je trouve ça très abstraits et j'aimerais savoir dans quel situation on s'en sert.
Mercii !!
Je développe beaucoup en C++ et je ne vois absolument pas de quoi tu veux parler... Mais regarde le tuto "Présentation des classes de Traits et de Politiques en C++".
Cela n'a pas l'air d'être un concept très utilisé en C++, si tu es débutant ne te focalise pas dessus.
Ca marche, en fait c'est un concept que j'avais vu en cours c'est pour ça et je n'avais pas très bien compris la notion.
En industrie, il faudrait se focaliser plus sur l'approche Orienté Objet ?
Je dirais que ça dépend de ton problème. Si les classes de traits te permettent de simplifier ton code, utilise-les. Je n'ai presque jamais utilisé les classes traits (peut-être que j'aurais pu/du le faire), mais beaucoup plus souvent les classes de politiques qui me sont extrêmement utiles pour apporter du code depuis l'extérieur d'une fonction.
Edit: Je ne sais pas si c'est à proprement parler une classe de traits, mais je place presque toujours des alias de type dans mes classes, certains en public. Ca m'est également extrêmement utile pour récupérer de l'information de type depuis l'extérieur. La classe fait pleins d'autres choses qu'établir des correspondances de types, donc je ne sais pas comment qualifier cet usage.
Quoiqu'il en soit mon -énorme- erreur de débutant avait été de connaître et d'utiliser uniquement la programmation orienté objet pour mettre de la flexibilité dans le code. Ca c'est retourné contre moi en très peu de temps, car si on ne connait que la POO on finit par écrire du code très compliqué, avec des hiérarchies de classes énormes et bien rigides, où rien ne veut plus rien dire. L'héritage est une relation de dépendance très forte donc prudence, que tu sois en industrie ou pas
Ce forum m'a appris à plutôt aller voir du côté des templates, et ça m'a bien simplifié la vie !
Bon courage,
C'est un concept abstrait puisque ça touche à la méta-programmation.
Un trait/tag/politic sera un type que tu passes en template pour paramétrer un objet.
Par exemple le 3° paramètre de std::map qui définit l'opérateur de comparaison, en le modifiant tu changes l'ordre d'insertion/de stockage des éléments de ta map.
Autre exemple simplet, changer le type de logging selon un trait
Au lieu d'une spécialisation via NoLog, on pourrait spécialiser le Logger pour void
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 class LogToFile { public: LogToFile() { mFile.open("log.txt"); void log(const char* str) { mFile<<str; } private: std::ofstream mFile; }; class LogToCout { public: void log(const char* str) { std::cout << str; } }; class NoLog { public: void log(const char*) {} }; template<class Log> class Logger { public: void log(const char* str) { mLog.log(str); } private: Log mLog; };
Et tu as tout plein de spécialisation pour avoir des infos sur les types via type_traits.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 template<> class Logger<void> { public: void log(const char*) {} };
Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
Un peu de programmation réseau ?
Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.
Ce que l'on appelle trait/tag/politic c'est juste l'utilisation d'un template ?
Bizarre, il me semble l'avoir expliqué il y a pas longtemps.
Dans tous les cas, cela sert à l’écriture de code générique (template).
.
.
.
Message retrouvé dans un forum concurrent::
tag et politiques sous-entendent des choses différentes, en plus de traits.
Un trait va renvoyer une info sur un type (cf l'itérator trait) -- le type peut être un tag. Un tag est une structure vide qui va servir à dispatcher via spécialisation template & cie. Une politique ressemble au trait sauf qu'il y a un comportement en plus attaché -- cf le vieux /C++ Modern Design/ d'Andrei Alexandrescu.
Ca sert tous les jours quand on utilise des vecteurs, mais ce n'est pas visible de l'utilisateur final. De temps en temps je m'en sers industriellement, mais une fois de plus, c'est pour écrire du code générique de type boute à outils. Tout le monde n'a pas mettre les mains dedans.
Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...
Salut,
De manière générale, un trait est une sorte "d'adaptateur" qui permet de déterminer quel sera le type réel d'un terme clairement défini.
Par exemple, si je crées un trait proche deet que je l'utilise sous la forme deusing IntTrait = MyTrait<int>;, les types représentés par IntTrait::value_type et celui de IntTrait::return_type seront tous les deux équivalents de int.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 template <typename V, R = V> struct MyTrait{ using value_type = V; using return_type = R };
Par contre, si je l'utilise sous la forme de using IntDoubleTrait = MyTrait<int, double>; le type représenté par IntDoubleTrait::value_type correspondra à int et celui représenté par IntDoubleTrait::return_type sera équivalent à double.
Et je pourrais très bien en fournir une spécialisation proche deA partir de là, vient la notion de politic permet de définir l'interface et -- le cas échéant -- des comportements adaptés à certaines situations et / ou à certains types de données. En cas de besoin, elles peuvent sans aucun problème faire appel à des traits afin de déterminer le type de certains éléments.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 template <> struct MyTrait<std::map<int, std::string>, std::map<int, std::string>>{ using value_type = int; using return_type = typename std::map<int, std::string>::const_iterator; };
Par exemple, je pourrais créer une politique proche dequi exposera une fonction foo prenant deux paramètres de type V sous la forme de référence constante et qui renverra un élément de type R.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 template <typename V, typename R = V> Policy{ static R foo(V const & a, V const & b); };
Mais si je veux l'utiliser avec une std::map<int, std::string>, je vais avoir quelque problèmes, parce que R et V ne correspondent finalement à rien dans une std::map.
Je vais donc passer par l'adaptateur (mon trait MyTrait) que j'ai "justement" spécialisé pour le traitement de cette drole de bête;
Et devines quoi? la magie opère :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 template <typename V, typename R = V> Policy{ usign value_type = typename MyTrait<V, R>::value_type; using return_type = typename MyTrait<V, R>::return_type; static R foo(V const & a, V const & b); };
- si j'utilise Policy avec un (ou deux) int comme paramètre template, Policy::value_type et Policy::return_type seront tous les deux équivalent au type int
- si j'utilise Policy avec un int et un double, Policy::value_type sera équivalent à int et Policy::return_type sera équivalent à double
- si j'utilise Policy avec un (ou deux) std::map<int, std::string>, Policy::value_type sera équivalent à... int et Policy::return_type sera équivalent à ... std::map<int, std::string>::const_iterator
Il n'y a plus que la notion de tag à voir.
Tu le sais sans doute, chaque paramètre template de ton expression (template) intervient dans l'évaluation finale. Par exemple,
te définira des types différents si T et/ou U sont de types différents:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 template <typename T, typename U> struct MyStruct{ T x; U y; };
- MaStruct<int, double> S1 et MaStruct<int, double> S2 définis des données de même type parce que les paramètres template sont identique deux à deux, mais
- MaStruct<int, int> S3 et MaStruct<int, double> S4 définis des données de type différent, parce qu'il y a (au moins) un paramètre template qui diffère.
Cela nous conviendra très souvent parfaitement. Mais, il y a quelques circonstances dans lesquelles on pourrait souhaiter que S1 et S2 correspondent à des données... de type différents. Ne serait-ce que parce que ma structure MyStruct peut servir à représenter une quantité invraissemblable de données qui n'ont rien à voir les unes avec les autres, même lorsque x est un int et que y est un double.
Il me faut donc disposer d'un moyen de faire la différence entre une MyStruct<int, double> qui représente respectivement le maximum de vie et le niveau "courant" de vie et une MyStrut<int, double> qui représente respectivement le niveau maximum de mana et ... le niveau courant de mana.
C'est très simple à faire : il me suffit de rajouter un paramètre template (un "tag") à MyStruct. C'est un élément qui ne sert à rien d'autre qu'à... permettre de faire la distinction entre une MyStruct<int, double> et une autre MyStruct<int, double> qui sera utilisée dans un contexte différent:
Si nous avons deux données pour lequel ce dernier paramètre template est identique, nous aurons affaire à deux donées de type identique, que nous pourrons utiliser "conjointement". Si ce dernier paramètre template est différent, nous devrons utiliser nos données dans des contextes différnts.
Ce que l'on appelle trait et politic sera souvent un élément template, avec ou sans spécialisation partielle ou totale.
Pour ce qui est des tags, qui n'ont -- comme je viens de l'expliquer -- pour seul objectif que de permettre de faire la distinction entre deux spécialisation d'une classe ou d'une structure en cas de besoin, le seul impératif, c'est qu'ils soient représentés par "quelque chose" qui est connu par le compilateur.
Cela peut être:
- une valeur numérique entière connue comme une constante de compilation, par exemple au travers d'un code proche deOu cela peut être un type de donnée (une structure), à ceci près que, comme cela ne servira qu'à la compilation, il ne sert absolument à rien qu'il contienne des données. Nous pourrons donc définir des structures vides pour représenter nos différents tags, sous une forme proche de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 enum Characteristics{ live, mana, strenght, agility, /*...*/ }; template <typename T, typename U, int Tag> struct MyStruct{ T x; U y; }; using live_infos = MyStruct<int, double, live>; using mana_infos = MyStruct<int, double, mana>; using strength_infos = MyStruct<int, double, strenght>; using agility_type = MyStruct<int, double, agility>;et nous obtiendront un résultat exactement équivalent.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 struct LiveTag{}; struct ManaTag{}; struct StrenghtTag{}; struct AgilityTag{}; using live_infos = MyStruct<int, double, LiveTag>; using mana_infos = MyStruct<int, double, ManaTag>; using strength_infos = MyStruct<int, double, StrenghtTag>; using agility_type = MyStruct<int, double, AgilityTag>;
L'énorme avantage des structures vides, c'est qu'il est tout à fait possible de rajouter un tag (et l'alias de type qui va avec pour les informations) à peu près n'importe où dans le code, sans avoir besoin d'aller modifier l'énumération, et donc de mieux respecter l'OCP.
Mais, comme rien n'est gratuit en ce bas monde, cet avantage présente également l'inconvénient de -- justement -- "éparpilller" les différents tags dans le code; même s'il est "tout à fait possible" de les regrouper dans un fichier d'en-tête unique![]()
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
C'est parfait merci beaucoup pour l'explication !!![]()
Partager