Quelque chose que je me suis souvent retrouvé à faire ces derniers temps est de déclarer des typedefs pertinents pour une classe particulière à l'intérieur de cette classe, c'est-à-dire
class Lorem
{
typedef boost::shared_ptr<Lorem> ptr;
typedef std::vector<Lorem::ptr> vector;
//
// ...
//
};
Ces types sont ensuite utilisés ailleurs dans le code:
Lorem::vector lorems;
Lorem::ptr lorem( new Lorem() );
lorems.push_back( lorem );
Raisons pour lesquelles je l'aime:
- Il réduit le bruit introduit par les modèles de classe,
std::vector<Lorem>
devientLorem::vector
, etc. - Il sert de déclaration d'intention - dans l'exemple ci-dessus, la classe Lorem est destinée à être comptée par référence
boost::shared_ptr
et stockée dans un vecteur. - Cela permet à l'implémentation de changer - c'est-à-dire si Lorem devait être changé pour être compté de manière intrusive (via
boost::intrusive_ptr
) à un stade ultérieur, cela aurait un impact minimal sur le code. - Je pense qu'il a l'air plus joli et est sans doute plus facile à lire.
Raisons pour lesquelles je n'aime pas ça:
- Il y a parfois des problèmes avec les dépendances - si vous voulez incorporer, par exemple, une
Lorem::vector
dans une autre classe mais que vous avez seulement besoin (ou voulez) de transmettre la déclaration de Lorem (par opposition à l'introduction d'une dépendance sur son fichier d'en-tête), vous finissez par devoir utiliser le types explicites (par exempleboost::shared_ptr<Lorem>
plutôt queLorem::ptr
), ce qui est un peu incohérent. - Ce n'est peut-être pas très courant et donc plus difficile à comprendre?
J'essaie d'être objectif avec mon style de codage, donc ce serait bien d'avoir d'autres opinions à ce sujet afin que je puisse disséquer un peu ma pensée.
la source
C'est exactement ce qu'il ne fait pas .
Si je vois 'Foo :: Ptr' dans le code, je n'ai absolument aucune idée si c'est un shared_ptr ou un Foo * (STL a des typedefs :: pointer qui sont T *, rappelez-vous) ou autre. Esp. s'il s'agit d'un pointeur partagé, je ne fournis pas du tout de typedef, mais je garde l'utilisation de shared_ptr explicitement dans le code.
En fait, je n'utilise presque jamais de typedefs en dehors de la métaprogrammation des modèles.
La conception STL avec des concepts définis en termes de fonctions membres et de typedefs imbriqués est un cul-de-sac historique, les bibliothèques de modèles modernes utilisent des fonctions libres et des classes de traits (cf. Boost.Graph), car elles n'excluent pas les types intégrés de modéliser le concept et parce qu'il facilite l'adaptation de types qui n'ont pas été conçus avec les concepts des bibliothèques de modèles donnés à l'esprit.
N'utilisez pas la STL comme une raison de faire les mêmes erreurs.
la source
std::allocator_traits<Alloc>
classe ... vous n'avez pas à la spécialiser pour chaque allocateur que vous écrivez, car elle emprunte simplement les types directement à partir deAlloc
.Alloc
qu'auteur, il n'est pas vraiment plus difficile de se spécialiserstd::allocator_traits<>
pour son nouveau type que d'ajouter les typedefs nécessaires. J'ai également modifié la réponse, car ma réponse complète ne correspondait pas à un commentaire.allocator_traits
essayer de créer un allocateur personnalisé, je n'ai pas déranger les quinze membres de la classe de traits ... tout ce que je dois faire est de diretypedef Blah value_type;
et de fournir les fonctions membres appropriées et la valeur par défautallocator_traits
sera comprendre la du repos. De plus, regardez votre exemple de Boost.Graph. Oui, il fait un usage intensif de la classe de traits ... mais l'implémentation par défaut degraph_traits<G>
requêtes simplesG
pour ses propres typedefs internes.iterator_traits
classe pour que vos algorithmes génériques puissent facilement rechercher les informations appropriées. Ce qui, encore une fois, demande par défaut à l'itérateur ses propres informations. En résumé, les traits et les typedefs internes ne s'excluent guère mutuellement ... ils se soutiennent.iterator_traits
est devenu nécessaire car ilT*
devrait être un modèle deRandomAccessIterator
, mais vous ne pouvez pas insérer les typedefs requisT*
. Une fois que nous l'avons faititerator_traits
, les typedefs imbriqués sont devenus redondants, et j'aurais aimé qu'ils aient été supprimés sur-le-champ. Pour la même raison (impossibilité d'ajouter des typedefs internes),T[N]
ne modélise pas leSequence
concept STL , et vous avez besoin de kludges tels questd::array<T,N>
. Boost.Range montre comment définir un concept de séquence moderne qui peut êtreT[N]
modélisé, car il ne nécessite pas de typedefs imbriqués, ni de fonctions membres.Les typedefs sont ceux sur lesquels la conception et les caractéristiques basées sur des politiques se sont construites en C ++, donc la puissance de la programmation générique en C ++ provient des typedefs eux-mêmes.
la source
Les typdefs sont définitivement du bon style. Et toutes vos «raisons que j'aime» sont bonnes et correctes.
À propos des problèmes que vous rencontrez avec ça. Eh bien, la déclaration prospective n'est pas un Saint Graal. Vous pouvez simplement concevoir votre code pour éviter les dépendances à plusieurs niveaux.
Vous pouvez déplacer typedef en dehors de la classe mais Class :: ptr est tellement plus joli que ClassPtr que je ne le fais pas. C'est comme avec les espaces de noms pour moi - les choses restent connectées dans le cadre.
Parfois j'ai fait
Et cela peut être par défaut pour toutes les classes de domaine et avec une certaine spécialisation pour certaines.
la source
La STL fait ce type de chose tout le temps - les typedefs font partie de l'interface de nombreuses classes de la STL.
sont tous des typedefs qui font partie de l'interface pour diverses classes de modèles STL.
la source
Un autre vote pour que ce soit une bonne idée. J'ai commencé à faire cela en écrivant une simulation qui devait être efficace, à la fois dans le temps et dans l'espace. Tous les types de valeur avaient un typedef Ptr qui a commencé comme un pointeur partagé de boost. J'ai ensuite fait un profilage et changé certains d'entre eux en un pointeur intrusif boost sans avoir à changer le code où ces objets étaient utilisés.
Notez que cela ne fonctionne que lorsque vous savez où les classes vont être utilisées et que toutes les utilisations ont les mêmes exigences. Je n'utiliserais pas cela dans le code de la bibliothèque, par exemple, car vous ne pouvez pas savoir lors de l'écriture de la bibliothèque le contexte dans lequel elle sera utilisée.
la source
Actuellement, je travaille sur du code, qui utilise intensivement ce type de typedefs. Jusqu'à présent, c'est bien.
Mais j'ai remarqué qu'il y a assez souvent des typedefs itératifs, les définitions sont réparties entre plusieurs classes, et on ne sait jamais vraiment de quel type on a affaire. Ma tâche est de résumer la taille de certaines structures de données complexes cachées derrière ces typedefs - je ne peux donc pas me fier aux interfaces existantes. En combinaison avec trois à six niveaux d'espaces de noms imbriqués, cela devient déroutant.
Alors avant de les utiliser, il y a quelques points à considérer
la source
Je recommande de déplacer ces typedefs en dehors de la classe. De cette façon, vous supprimez la dépendance directe sur les classes de pointeur et de vecteur partagées et vous ne pouvez les inclure que si nécessaire. À moins que vous n'utilisiez ces types dans votre implémentation de classe, je considère qu'ils ne devraient pas être des typedefs internes.
Les raisons pour lesquelles vous l'aimez sont toujours concordantes, car elles sont résolues par l'alias de type via typedef, pas en les déclarant dans votre classe.
la source
Lorsque le typedef est utilisé uniquement dans la classe elle-même (c'est-à-dire qu'il est déclaré privé), je pense que c'est une bonne idée. Cependant, pour exactement les raisons que vous donnez, je ne l'utiliserais pas si le typedef doit être connu en dehors de la classe. Dans ce cas, je recommande de les déplacer en dehors de la classe.
la source