51 votes

Fichier d'en-tête des meilleures pratiques pour les typedefs

Je suis l'aide de shared_ptr et STL largement dans un projet, et cela conduit à de trop longues, le risque d'erreur de type shared_ptr< vector< shared_ptr<const Foo> > > (je suis un ObjC programmeur de préférence, où les noms longs sont la norme, et encore c'est beaucoup trop.) Il serait beaucoup plus clair, je crois, à l'appel de cette FooListPtr et la documentation de la convention de nommage "Ptr" signifie shared_ptr et "Liste", vecteur de shared_ptr.

C'est facile de typedef, mais elle provoque des maux de tête avec les en-têtes. Il me semble avoir plusieurs options d'où définissent FooListPtr:

  • Foo.h. Qui entrelace tous les en-têtes et crée de graves problèmes de compilation, c'est donc un non-starter.
  • FooFwd.h ("forward-tête"). C'est ce Efficace C++ indique, basé sur iosfwd.h. C'est très cohérent, mais la surcharge de maintien de deux fois le nombre d'en-têtes semble ennuyeux au possible.
  • Commun.h (mettre tous ensemble dans un seul fichier). Cela tue la réutilisabilité en enlacent beaucoup de sans rapport avec les types. Maintenant, vous ne pouvez pas juste prendre un objet et le déplacer vers un autre projet. C'est un non-starter.
  • Une sorte de fantaisie #define magie qui typedef s'il n'a pas déjà été typedefed. J'ai un respectueux de l'aversion pour le préprocesseur parce que je pense qu'il rend difficile pour les nouvelles personnes pour analyser le code, mais peut-être que....
  • L'utilisation d'un vecteur sous-classe plutôt qu'à une définition de type. Cela semble dangereux...

Sont là les meilleures pratiques ici? Comment peuvent-ils transformer en véritable code, lorsque la réutilisabilité, la lisibilité et la cohérence sont d'une importance primordiale?

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

14voto

AshleysBrain Points 11439

Je suis de la programmation sur un projet qui sonne comme il utilise l' common.h méthode. Il fonctionne très bien pour ce projet.

Il y a un fichier appelé ForwardsDecl.h qui est dans l'en-tête précompilé et simplement de l'avant-déclare que toutes les classes et nécessaire typedefs. Dans ce cas - unique_ptr est utilisé à la place de shared_ptr, mais l'utilisation doit être similaire. Il ressemble à ceci:

// Forward declarations
class ObjectA;
class ObjectB;
class ObjectC;

// List typedefs
typedef std::vector<std::unique_ptr<ObjectA>> ObjectAList;
typedef std::vector<std::unique_ptr<ObjectB>> ObjectBList;
typedef std::vector<std::unique_ptr<ObjectC>> ObjectCList;

Ce code est accepté par Visual C++ 2010, même si les classes ne sont que l'avant-déclarée (l'ensemble des définitions de classe ne sont pas nécessaires, donc il n'y a pas besoin d'inclure chaque classe d'en-tête de fichier). Je ne sais pas si c'est standard et d'autres compilateurs nécessitent la définition de la classe, mais il est utile qu'il n'a pas: une autre classe (ObjectD) peuvent avoir un ObjectAList en tant que membre, sans avoir besoin d'inclure ObjectA.h - ce qui peut vraiment aider à réduire l'en-tête de fichier de dépendances!

L'entretien n'est pas particulièrement un problème, parce que les attaquants déclarations seulement besoin d'être écrit une fois, et de toute modification ultérieure seulement besoin de se produire dans l'intégralité de la déclaration dans le fichier d'en-tête (et cela va déclencher moins de fichiers source pour être recompilé en raison de la réduction des dépendances).

Enfin, il apparaît que ce peut être partagé entre les projets (je n'ai pas essayé moi-même) parce que, même si un projet n'est pas réellement déclarer un ObjectA, il n'a pas d'importance parce que c'était seulement les attaquants déclaré et si vous ne l'utilisez pas, le compilateur n'a pas de soins. Par conséquent, le fichier peut contenir les noms de classes dans tous les projets qu'il est utilisé, et il n'a pas d'importance si certaines sont manquantes pour un projet particulier. Tout ce qui est nécessaire est nécessaire intégralité de la déclaration de l'en-tête (par exemple, ObjectA.h) est incluse dans la source (.rpc) des fichiers qui en fait usage .

6voto

Konrad Rudolph Points 231505

Je voudrais aller avec une approche combinée de l'avant-têtes et une sorte d' common.h d'en-tête qui est spécifique à votre projet et à seulement comprend la totalité de la déclaration anticipée en-têtes et de toute autre substance qui est commun et léger.

Vous se plaignent de la surcharge de maintien de deux fois le nombre d'en-têtes, mais je ne pense pas que ce soit trop un problème: l'avant-têtes habituellement seulement besoin de connaître un nombre très limité de types (?), et parfois, même pas le type complet.

Vous pouvez même essayer de l'auto-générer les en-têtes à l'aide d'un script (ce qui est fait par exemple dans SeqAn) si il y a vraiment que de nombreux en-têtes.

4voto

peterchen Points 21792

+1 pour la documentation de la définition de type de conventions.

  • Foo.h - pouvez-vous détailler les problèmes que vous avez avec qui?
  • FooFwd.h - je n'avais pas les utiliser en général, que sur des "évidences points chauds". (Oui, les "points chauds" sont difficiles à déterminer). Il ne change pas les règles de l'OMI, parce que quand vous faites introduire un ta-tête, les typedefs de foo.h s'y déplacer.
  • Commun.h - cool pour les petits projets, mais n'est pas à l'échelle, je suis d'accord.
  • Une sorte de fantaisie #define... s'il vous PLAÎT, NON!...
  • L'utilisation d'un vecteur sous-classe - ne fait pas mieux. Vous pouvez utiliser de confinement.

Voici donc le prelimenary suggestions (révisé à partir de cette autre question..)

  1. Type Standard, en-têtes <boost/shared_ptr.hpp>, <vector> etc. peut aller dans un en-tête précompilé / shared fichier include pour le projet. Ce n'est pas mauvais. (Personnellement, j'ai toujours inclure, si nécessaire, mais qui fonctionne en plus de les mettre dans le PCH.)

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

  3. Associé types (comme FooListPtr) aller à l'endroit où Foo est declarated, si le type associé est la principale utilisation de ce type. C'est presque toujours le cas pour certains types - par exemple, shared_ptr.

  4. Si Foo obtient un séparé de l'avant de la déclaration de l'en-tête, et le type associé est ok avec ça, il se déplace à la FooFwd.h, trop.

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

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

Il se sent "évident" pour moi, mais je suis d'accord c'est pas bon comme une norme de codage.

3voto

justin Points 72871

Je suis l'aide de shared_ptr et STL largement dans un projet, et cela conduit à de trop longues, le risque d'erreur de types comme shared_ptr< vector< shared_ptr > > (je suis un ObjC programmeur de préférence, où les noms longs sont la norme, et encore c'est beaucoup trop.) Il serait beaucoup plus clair, je crois, à l'appel de cette FooListPtr et la documentation de la convention de nommage "Ptr" signifie shared_ptr et "Liste", vecteur de shared_ptr.

pour commencer, je vous recommande d'utiliser une bonne conception des structures pour la détermination de la portée (par exemple, les espaces de noms) ainsi que les descriptifs, le non-abrégée des noms pour les typedefs. FooListPtr est terriblement court, de l'omi. personne ne veut de deviner ce qu'est une abréviation qui signifie (ou être surpris de trouver des Foo est const, partagées, etc.), et personne ne veut de modifier leur code, simplement en raison de la portée des collisions.

il peut également aider à 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 de leurs déclaré portée:

namespace MON {
namespace Diddy {
class Foo;
} /* << Diddy */

/*...*/
typedef Diddy::Foo Diddy_Foo;

} /* << MON */

il y a des exceptions à cela:

  • un tout ecapsualted type privé
  • un type au sein d'une nouvelle portée

pendant que nous y sommes, using en l'espace de noms d'étendues et de l'espace de noms d'alias doit être évitée - qualifier le champ si vous souhaitez à l'avenir pour réduire maintentance.

C'est facile de typedef, mais elle provoque des maux de tête avec les en-têtes. Il me semble avoir plusieurs options d'où pour définir FooListPtr:

Foo.h. Qui entrelace tous les en-têtes et crée de graves problèmes de compilation, c'est donc un non-starter.

il peut être une option pour les déclarations qui dépendent d'autres déclarations. ce qui implique que vous avez besoin de diviser les paquets, ou qu'il y est une commune localisée d'interface pour les sous-systèmes.

FooFwd.h ("forward-tête"). C'est ce Efficace C++ indique, basé sur iosfwd.h. C'est très cohérent, mais la surcharge de maintien de deux fois le nombre d'en-têtes semble ennuyeux au possible.

ne vous inquiétez pas à propos de la maintenance de cela, vraiment. c'est une bonne pratique. le compilateur utilise avant les déclarations et les typedefs avec très peu d'effort. ce n'est pas gênant car il permet de réduire vos dépendances, et permet de s'assurer qu'ils sont tous corrects et visible. il n'y a vraiment pas plus à maintenir puisque les autres fichiers, reportez-vous à 'l'ensemble des types d'en-tête".

Commun.h (mettre tous ensemble dans un seul fichier). Cela tue la réutilisabilité en enlacent beaucoup de sans rapport avec les types. Maintenant, vous ne pouvez pas juste prendre un objet et le déplacer vers un autre projet. C'est un non-starter.

en fonction des dépendances d'inclusions sont excellents (idéal, vraiment) - ne pas négliger. vous devez évidemment avoir pour créer un package d'interfaces (ou bibliothèques) qui sont conçus et bien structuré, et de représenter les classes connexes de composants. vous êtes en rendant inutile question de l'objet/la réutilisation des composants. minimiser la statique des données d'une bibliothèque, et de laisser le lien et la bande phases de 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/packages sont bien conçus).

Une sorte de fantaisie #define magie qui typedef s'il n'a pas déjà été typedefed. J'ai un respectueux de l'aversion pour le préprocesseur parce que je pense qu'il rend difficile pour les nouvelles personnes pour analyser le code, mais peut-être que....

en fait, vous pouvez déclarer un typedef dans le même champ d'application plusieurs fois (par exemple, dans deux en-têtes) - qui n'est pas une erreur.

déclarer un typedef dans le même champ d'application avec différents types de sous-jacents est une erreur. évidemment. vous devez éviter cela, et heureusement, le compilateur impose.

pour éviter cela, créer une "traduction construire" qui comprend le monde - le compilateur signalera les déclarations de typedeffed types qui ne correspondent pas.

en essayant de se faufiler par avec un minimum de typedefs et/ou vers l'avant (qui sont assez proches pour gratuit lors de la compilation) ne vaut pas l'effort. parfois, vous aurez besoin d'un tas de soutien conditionnel de déclaration - une fois défini, il est facile stl (bibliothèques sont un bon exemple de cela-dans le cas où vous êtes également en avant en déclarant template<typename,typename>class vector;).

il est préférable d'avoir toutes ces déclarations visible pour intercepter les erreurs immédiatement, et vous pouvez éviter le préprocesseur dans ce cas comme un bonus.

L'utilisation d'un vecteur sous-classe plutôt qu'à une définition de type. Cela semble dangereux...

une sous-classe de std::vector est souvent signalé comme une "erreur de débutant". ce conteneur n'était pas censé être sous-classé. n'ayez pas recours à des mauvaises pratiques simplement de réduire les temps de compilation/dépendances. si la dépendance est vraiment important, vous devriez probablement utiliser des PIMPL, de toute façon:

// <package>.types.hpp
namespace MON {
class FooListPtr;
}

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

Sont là les meilleures pratiques ici? Comment peuvent-ils transformer en véritable code, lorsque la réutilisabilité, la lisibilité et la cohérence sont d'une importance primordiale?

en fin de compte, j'ai trouvé une petite concis paquet approche fondée sur le meilleur, pour les réutiliser, réduire les temps de compilation, et en réduisant la dépendance.

1voto

Michael Daum Points 589

Malheureusement, avec des typedefs vous avez à choisir entre le pas les options idéales pour vos fichiers d'en-tête. Il existe des cas particuliers où la première option (à droite dans l'en-tête de classe) fonctionne bien, mais il semble que cela ne fonctionnera pas pour vous. Il y a aussi des cas où la dernière option fonctionne bien, mais il est généralement lorsque 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 de l'avant de déclarer d'en-tête de la solution. Il y a saisie de texte supplémentaire et les frais généraux, mais il ne serait pas de C++ sinon, droite? Il garde les choses distinct, propre et rapide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X