Meilleures pratiques du fichier d’en-tête pour les typedefs

J’utilise largement les fichiers shared_ptr et STL dans un projet, ce qui conduit à des types trop longs, sujets aux erreurs, comme shared_ptr< vector< shared_ptr > > >> (je suis un programmeur ObjC de préférence, où les noms longs Je pense que c’est bien d’appeler cela FooListPtr et de documenter la convention de dénomination selon laquelle “Ptr” signifie shared_ptr et “List” signifie vecteur de shared_ptr.

Ceci est facile à dactylographier, mais cela cause des maux de tête avec les en-têtes. Je semble avoir plusieurs options pour définir FooListPtr :

  • Foo.h. Cela mêle tous les en-têtes et crée de sérieux problèmes de construction, donc c’est un non-starter.
  • FooFwd.h (“en-tête avant”). C’est ce que suggère Effective C ++ , basé sur iosfwd.h. C’est très cohérent, mais la nécessité de maintenir deux fois le nombre d’en-têtes semble au mieux gênante.
  • Common.h (tous ensemble dans un seul fichier). Cela tue la réutilisation en enchevêtrant beaucoup de types non liés. Vous ne pouvez pas maintenant choisir un object et le déplacer vers un autre projet. C’est un non-starter.
  • Une sorte de magie fantaisiste fantaisiste que typedef a si elle n’a pas déjà été typée. J’ai une aversion constante pour le préprocesseur parce que je pense que cela rend difficile pour les nouveaux utilisateurs de comprendre le code, mais peut-être ….
  • Utilisez une sous-classe de vecteurs plutôt qu’un typedef. Cela semble dangereux …

Y a-t-il des meilleures pratiques ici? Comment se transforment-ils en code réel, lorsque la réutilisabilité, la lisibilité et la cohérence sont primordiales?

J’ai marqué ce wiki de communauté si d’autres veulent append des options supplémentaires pour la discussion.

Je programme sur un projet qui ressemble à la méthode common.h . Cela fonctionne très bien pour ce projet.

Il y a un fichier appelé ForwardsDecl.h qui se trouve dans l’en-tête pré-compilé et qui déclare simplement toutes les classes importantes et les types de caractères nécessaires. Dans ce cas, unique_ptr est utilisé à la place de shared_ptr , mais l’utilisation doit être similaire. Cela ressemble à ceci:

 // Forward declarations class ObjectA; class ObjectB; class ObjectC; // List typedefs typedef std::vector> ObjectAList; typedef std::vector> ObjectBList; typedef std::vector> ObjectCList; 

Ce code est accepté par Visual C ++ 2010 même si les classes ne sont que déclarées en avant (les définitions de classe complètes ne sont pas nécessaires, il n’est donc pas nécessaire d’inclure le fichier d’en-tête de chaque classe). Je ne sais pas si cela est standard et les autres compilateurs nécessiteront la définition complète de la classe, mais il est utile que ce ne soit pas le cas: une autre classe (ObjectD) peut avoir un ObjectAList en tant que membre, sans devoir inclure ObjectA.h vraiment aider à réduire les dépendances de fichiers d’en-tête!

La maintenance n’est pas particulièrement un problème, car les déclarations à terme doivent être écrites une seule fois, et toutes les modifications ultérieures ne doivent se produire que dans la déclaration complète du fichier d’en-tête de la classe. dépendances).

Enfin, il semble que cela puisse être partagé entre les projets (je n’ai pas essayé moi-même) car même si un projet ne déclare pas réellement d’objectA, cela n’a aucune importance car il n’a été déclaré que comme un renvoi et si vous ne l’utilisez pas ne se soucie pas. Par conséquent, le fichier peut contenir les noms des classes dans tous les projets dans lesquels il est utilisé, et peu importe si certains sont manquants pour un projet particulier. Tout ce qui est requirejs est que l’en-tête de déclaration complète nécessaire (par exemple, ObjectA.h ) soit inclus dans tous les fichiers source (.cpp) qui les utilisent réellement .

Je voudrais aller avec une approche combinée d’en-têtes avant et une sorte d’en common.h tête common.h qui est spécifique à votre projet et inclut seulement tous les en-têtes de déclaration directe et tout autre élément commun et léger.

Vous vous plaignez de la nécessité de conserver deux fois plus d’en-têtes, mais je ne pense pas que cela pose un problème majeur: les en-têtes avant ont généralement besoin de connaître un nombre très limité de types (un?) Et parfois même pas le type complet

Vous pourriez même essayer de générer automatiquement les en-têtes à l’aide d’un script (cela se fait par exemple dans SeqAn ) s’il y a vraiment autant d’en-têtes.

+1 pour documenter les conventions typedef.

  • Foo.h – pouvez-vous détailler les problèmes que vous avez avec ça?
  • FooFwd.h – Je ne les utiliserais généralement pas, uniquement sur des “points névralgiques évidents”. (Oui, les “points chauds” sont difficiles à déterminer). Il ne modifie pas les règles IMO car lorsque vous introduisez un en-tête fwd, les typedefs associés de foo.h se déplacent là.
  • Common.h – cool pour les petits projets, mais pas à l’échelle, je suis d’accord.
  • Une sorte de fantaisie #define … S’IL VOUS PLAÎT NON! …
  • Utiliser une sous-classe vectorielle – ne le rend pas meilleur. Vous pouvez utiliser le confinement, cependant.

Donc, voici les suggestions préliminaires (révisées à partir de cette autre question ..)

  1. Les en-têtes de type standard , etc. peuvent entrer dans un fichier d’en-tête / partage partagé précompilé pour le projet. Ce n’est pas mal. (Personnellement, je les inclut toujours là où cela est nécessaire, mais cela fonctionne en plus de les intégrer au PCH.)

  2. Si le conteneur est un détail d’implémentation, les typedefs vont là où le conteneur est déclaré (par exemple, les membres de classe privés si le conteneur est un membre de classe privé)

  3. Les types associés (comme FooListPtr ) vont à l’endroit où Foo est déclaré, si le type associé est l’utilisation principale du type. C’est presque toujours vrai pour certains types – par exemple, shared_ptr .

  4. Si Foo obtient un en-tête de déclaration de transfert séparé, et que le type associé est correct, il passe également à FooFwd.h.

  5. Si le type est uniquement associé à une interface particulière (par exemple, paramètre pour une méthode publique), il y va.

  6. Si le type est partagé (et ne répond à aucun des critères précédents), il obtient son propre en-tête. Notez que cela signifie également de tirer dans toutes les dépendances.

Cela me semble évident, mais je suis d’accord pour dire que ce n’est pas une norme de codage.

J’utilise abondamment shared_ptr et STL dans un projet, ce qui conduit à des types trop longs, sujets aux erreurs, comme shared_ptr > (je suis un programmeur ObjC de préférence, où les noms longs sont la norme, et c’est beaucoup trop.) Il serait beaucoup plus clair, je crois, d’appeler systématiquement cette FooListPtr et de documenter la convention de nommage selon laquelle “Ptr” signifie shared_ptr et “List” signifie vecteur de shared_ptr.

pour les débutants, je recommande d’utiliser de bonnes structures de conception pour la scope (par exemple, les espaces de noms) ainsi que des noms descriptifs et non abrégés pour les typedefs. FooListPtr est terriblement court, imo. personne ne veut deviner ce que signifie une abréviation (ou être surpris de constater que Foo est const, partagé, etc.), et que personne ne veut modifier son code simplement à cause de collisions de scope.

il peut également être utile de choisir un préfixe pour les typedefs dans vos bibliothèques (ainsi que d’autres catégories communes).

c’est aussi une mauvaise idée de faire glisser les types hors de leur scope déclarée:

 namespace MON { namespace Diddy { class Foo; } /* << Diddy */ /*...*/ typedef Diddy::Foo Diddy_Foo; } /* << MON */ 

il y a des exceptions à ceci:

  • un type privé entièrement ecapsualted
  • un type contenu dans une nouvelle scope

Pendant que nous y sums, il convient d'éviter d' using étendues d'espaces de noms et des alias d'espaces de noms - qualifiez la scope si vous souhaitez minimiser la maintenance future.

Ceci est facile à dactylographier, mais cela cause des maux de tête avec les en-têtes. Je semble avoir plusieurs options pour définir FooListPtr:

Foo.h. Cela mêle tous les en-têtes et crée de sérieux problèmes de construction, donc c'est un non-starter.

cela peut être une option pour les déclarations qui dépendent réellement d'autres déclarations. ce qui implique que vous devez diviser les paquets ou qu'il existe une interface localisée commune pour les sous-systèmes.

FooFwd.h ("en-tête avant"). C'est ce que suggère Effective C ++, basé sur iosfwd.h. C'est très cohérent, mais la nécessité de maintenir deux fois le nombre d'en-têtes semble au mieux gênante.

ne vous inquiétez pas du maintien de cela, vraiment. c'est une bonne pratique. le compilateur utilise des déclarations avancées et des typedefs avec très peu d'effort. Ce n'est pas gênant car cela permet de réduire vos dépendances et de vous assurer qu'elles sont toutes correctes et visibles. il n'y a vraiment plus rien à maintenir puisque les autres fichiers font référence à l'en-tête 'types de paquets'.

Common.h (tous ensemble dans un seul fichier). Cela tue la réutilisation en enchevêtrant beaucoup de types non liés. Vous ne pouvez pas maintenant choisir un object et le déplacer vers un autre projet. C'est un non-starter.

les dépendances et les inclusions basées sur des paquets sont excellentes (idéales, vraiment) - ne l'excluez pas. vous devrez évidemment créer des interfaces de paquetages (ou bibliothèques) conçues et structurées correctement et représentant des classes de composants connexes. vous faites un problème inutile de réutilisation object / composant. minimiser les données statiques d'une bibliothèque et laisser le lien et les phases de ssortingp faire leur travail. encore une fois, gardez vos paquets petits et réutilisables et ce ne sera pas un problème (en supposant que vos bibliothèques / paquets sont bien conçus).

Une sorte de magie fantaisiste fantaisiste que typedef a si elle n'a pas déjà été typée. J'ai une aversion constante pour le préprocesseur parce que je pense que cela rend difficile pour les nouveaux utilisateurs de comprendre le code, mais peut-être ....

en fait, vous pouvez déclarer un typedef dans la même scope plusieurs fois (par exemple, dans deux en-têtes séparés) - ce n'est pas une erreur.

déclarer un typedef dans la même étendue avec différents types sous-jacents est une erreur. évidemment. vous devez éviter cela, et heureusement, le compilateur applique cela.

pour éviter cela, créez un "build de traduction" qui inclut le monde - le compilateur marquera les déclarations de types typedeffed qui ne correspondent pas.

essayer de se faufiler avec un minimum de typedefs et / ou d’avance (qui sont assez proches pour être libres lors de la compilation) ne vaut pas la peine. parfois, vous aurez besoin d'un support conditionnel pour les déclarations avancées - une fois que cela est défini, c'est facile (les bibliothèques stl en sont un bon exemple - dans le cas où vous templateclass vector; aussi un templateclass vector; ).

il est préférable de simplement afficher toutes ces déclarations pour détecter immédiatement les erreurs éventuelles et, dans ce cas, vous pouvez éviter le préprocesseur.

Utilisez une sous-classe de vecteurs plutôt qu'un typedef. Cela semble dangereux ...

une sous-classe de std::vector est souvent signalée comme une "erreur du débutant". ce conteneur n'était pas destiné à être sous-classé. n'utilisez pas de mauvaises pratiques simplement pour réduire vos temps / dépendances de compilation. Si la dépendance est vraiment importante, vous devriez probablement utiliser PIMPL, de toute façon:

 // .types.hpp namespace MON { class FooListPtr; } // FooListPtr.hpp namespace MON { class FooListPtr { /* ... */ private: shared_ptr< vector< shared_ptr > > d_data; }; } 

Y a-t-il des meilleures pratiques ici? Comment se transforment-ils en code réel, lorsque la réutilisabilité, la lisibilité et la cohérence sont primordiales?

en fin de compte, j'ai trouvé une approche basée sur un petit paquet concis, la meilleure pour la réutilisation, pour réduire les temps de compilation et minimiser la dépendance.

Malheureusement, avec typedefs, vous devez choisir parmi les options non idéales pour vos fichiers d’en-tête. Il y a des cas particuliers où l’option 1 (directement dans l’en-tête de la classe) fonctionne bien, mais il semble que cela ne fonctionnera pas pour vous. Il existe également des cas où la dernière option fonctionne bien, mais c’est généralement là que vous utilisez la sous-classe pour remplacer un modèle impliquant une classe avec un seul membre de type std :: vector. Pour votre situation, j’utiliserais la solution d’en-tête déclarante. Il y a plus de frappe et de surcharge, mais ce ne serait pas C ++ sinon, non? Il garde les choses séparées, propres et rapides.