Séparer le code de classe C ++ en plusieurs fichiers, quelles sont les règles?

Temps de reflection – Pourquoi voulez-vous quand même scinder votre fichier?

Comme le titre l’indique, le dernier problème que j’ai est celui des erreurs de définition de lieurs multiples. J’ai effectivement résolu le problème, mais je ne l’ai pas corrigé correctement. Avant de commencer, je souhaite aborder les raisons du fractionnement d’un fichier de classe en plusieurs fichiers. J’ai essayé de mettre tous les scénarios possibles ici – si j’en manquais, rappelez-le-moi et je pourrai apporter des modifications. Espérons que les réponses suivantes sont correctes:

Raison 1 Pour économiser de l’espace:

Vous avez un fichier contenant la déclaration d’une classe avec tous les membres de la classe. Vous placez des gardes #include autour de ce fichier (ou #pragma une fois) pour éviter tout conflit si vous #incluez le fichier dans deux fichiers d’en-tête différents, qui sont ensuite inclus dans un fichier source. Vous comstackz un fichier source séparé avec l’implémentation des méthodes déclarées dans cette classe, car cela charge plusieurs lignes de code à partir de votre fichier source, ce qui nettoie un peu les choses et introduit un peu d’ordre dans votre programme.

Exemple: Comme vous pouvez le constater, l’exemple ci-dessous pourrait être amélioré en scindant l’implémentation des méthodes de classe dans un fichier différent. (Un fichier .cpp)

// my_class.hpp #pragma once class my_class { public: void my_function() { // LOTS OF CODE // CONFUSING TO DEBUG // LOTS OF CODE // DISORGANIZED AND DISTRACTING // LOTS OF CODE // LOOKS HORRIBLE // LOTS OF CODE // VERY MESSY // LOTS OF CODE } // MANY OTHER METHODS // MEANS VERY LARGE FILE WITH LOTS OF LINES OF CODE } 

Raison 2 Pour éviter les erreurs de lieur de définitions multiples:

C’est peut-être la raison principale pour laquelle vous sépareriez l’application de la déclaration. Dans l’exemple ci-dessus, vous pouvez déplacer le corps de la méthode en dehors de la classe. Cela lui donnerait l’air beaucoup plus propre et structuré. Toutefois, selon cette question , l’exemple ci-dessus comporte inline spécificateurs en inline implicites. Le déplacement de l’implémentation de la classe vers l’extérieur de la classe, comme dans l’exemple ci-dessous, entraînera des erreurs d’éditeur de liens. Vous devrez donc tout insérer en ligne ou déplacer les définitions de fonction dans un fichier .cpp.

Exemple: _L’exemple ci-dessous provoquera “plusieurs erreurs de l’éditeur de liens de définition” si vous ne déplacez pas la définition de la fonction dans un fichier .cpp ou ne spécifiez pas la fonction en ligne.

 // my_class.hpp void my_class::my_function() { // ERROR! MULTIPLE DEFINITION OF my_class::my_function // This error only occurs if you #include the file containing this code // in two or more separate source (comstackd, .cpp) files. } 

Pour résoudre le problème:

 //my_class.cpp void my_class::my_function() { // Now in a .cpp file, so no multiple definition error } 

Ou:

 // my_class.hpp inline void my_class::my_function() { // Specified function as inline, so okay - note: back in header file! // The very first example has an implicit `inline` specifier } 

Raison 3 Vous voulez économiser de l’espace, encore une fois, mais cette fois, vous travaillez avec une classe de modèle:

Si nous travaillons avec des classes de modèle, nous ne pouvons pas déplacer l’implémentation vers un fichier source (fichier .cpp). Ce n’est pas autorisé actuellement (je suppose) par les compilateurs standards ou actuels. Contrairement au premier exemple de Reason 2 ci-dessus, nous sums autorisés à placer l’implémentation dans le fichier d’en-tête. Selon cette question, la raison en est que les méthodes de classe template ont également inline spécificateurs implicites. Est-ce exact? (Cela semble logique.) Mais personne ne semblait savoir sur la question que je viens de mentionner!

Alors, les deux exemples ci-dessous sont-ils identiques?

 // some_header_file.hpp #pragma once // template class declaration goes here class some_class { // Some code }; // Example 1: NO INLINE SPECIFIER template void some_class::class_method() { // Some code } // Example 2: INLINE specifier used template inline void some_class::class_method() { // Some code } 

Si vous avez un fichier d’en-tête de classe de modèle, qui devient énorme en raison de toutes vos fonctions, je pense que vous êtes autorisé à déplacer les définitions de fonction dans un autre fichier d’en-tête (généralement un fichier .tpp?), Puis un #include file.tpp à la fin de votre fichier d’en-tête contenant la déclaration de classe. Cependant, vous ne devez PAS inclure ce fichier ailleurs, d’où le .tpp plutôt que le .hpp .

Je suppose que vous pourriez aussi faire cela avec les méthodes inline d’un cours normal? Est-ce permis aussi?

Heure des questions

J’ai donc fait quelques déclarations ci-dessus, dont la plupart concernent la structuration des fichiers sources. Je pense que tout ce que j’ai dit était correct, parce que j’ai fait des recherches de base et “découvert des trucs”, mais c’est une question et donc je ne sais pas à coup sûr.

Cela se résume à la façon dont vous organiseriez le code dans des fichiers. Je pense avoir mis au point une structure qui fonctionnera toujours.

Voici ce que je suis venu avec. (Ceci est “la structure d’organisation / structure de fichier de code de classe d’Ed Bird” , si vous voulez. Je ne sais pas si cela sera très utile, c’est le sharepoint demander.)

  • 1: Déclarez la classe (modèle ou autre) dans un fichier .hpp , y compris toutes les méthodes, fonctions ami et données.
  • 2: Au bas du fichier .hpp , #include un fichier .tpp contenant l’implémentation de toutes inline méthodes en inline . Créez le fichier .tpp et assurez-vous que toutes les méthodes sont en inline .
  • 3: Tous les autres membres (fonctions non inline, fonctions ami et données statiques) doivent être définis dans un fichier .cpp , qui #include est le fichier .hpp en haut pour éviter des erreurs telles que “la classe ABC n’a pas été déclarée”. Étant donné que tout le contenu de ce fichier aura un lien externe, le programme sera lié correctement.

Des normes comme celle-ci existent-elles dans l’indussortinge? Est-ce que le standard que j’ai créé fonctionnera dans tous les cas?

Vos trois points semblent à peu près correct. C’est la manière standard de faire les choses (bien que je n’ai jamais vu l’extension .tpp auparavant, c’est habituellement .inl), bien que personnellement je mette juste des fonctions inline au bas des fichiers d’en-tête plutôt que dans un fichier séparé.

Voici comment je range mes fichiers. J’omets le fichier de déclaration avant pour les classes simples.

myclass-fwd.h

 #pragma once namespace NS { class MyClass; } 

myclass.h

 #pragma once #include "headers-needed-by-header" #include "myclass-fwd.h" namespace NS { class MyClass { .. }; } 

myclass.cpp

 #include "headers-needed-by-source" #include "myclass.h" namespace { void LocalFunc(); } NS::MyClass::... 

Remplacez pragma par des protège-en-têtes selon vos préférences.

La raison de cette approche est de réduire les dépendances d’en-tête, ce qui ralentit les temps de compilation dans les grands projets. Si vous ne le saviez pas, vous pouvez transférer une classe à utiliser comme pointeur ou référence. La déclaration complète n’est nécessaire que lorsque vous construisez, créez ou utilisez des membres de la classe.

Cela signifie qu’une autre classe qui utilise la classe (prend les parameters par pointeur / référence) doit uniquement inclure l’en-tête fwd dans son propre en-tête. L’en-tête complet est ensuite inclus dans le fichier source de la deuxième classe. Cela réduit considérablement la quantité de déchets inutiles que vous obtenez en tirant dans une grosse en-tête, qui tire dans une autre grande en-tête, qui en tire une autre …

Le conseil suivant est l’espace de nom non nommé (parfois appelé espace de nom anonyme). Cela ne peut apparaître que dans un fichier source et c’est comme un espace de nom masqué visible uniquement par ce fichier. Vous pouvez placer ici des fonctions locales, des classes, etc. qui ne sont utilisées que par le fichier source. Cela évite les conflits de noms si vous créez quelque chose du même nom dans deux fichiers différents. (Deux fonctions locales F par exemple, peuvent donner des erreurs de l’éditeur de liens).

La principale raison de séparer l’interface de l’implémentation est de ne pas avoir à recomstackr tout votre code lorsque quelque chose change dans l’implémentation; il vous suffit de recomstackr les fichiers source qui ont changé.

Quant à “Déclarer la classe (modèle ou autre)”, un template n’est pas une class . Un template est un modèle pour la création de classes. Plus important encore, vous définissez une classe ou un modèle dans un en-tête. La définition de classe inclut les déclarations de ses fonctions membres, et les fonctions membres non inines sont définies dans un ou plusieurs fichiers sources. Les fonctions de membre en ligne et toutes les fonctions de modèle doivent être définies dans l’en-tête, quelle que soit la combinaison de définitions directes et de directives #include vous préférez.

Des normes comme celle-ci existent-elles dans l’indussortinge?

Oui. Là encore, les normes de codage qui sont assez différentes de celles que vous avez exprimées sont également présentes dans l’indussortinge. Vous parlez de normes de codage, après tout, et les normes de codage vont de bonnes à mauvaises à mauvaises.

Est-ce que le standard que j’ai créé fonctionnera dans tous les cas?

Absolument pas. Par exemple,

 template  class Foo { public: void some_method (T& arg); ... }; 

Ici, la définition du modèle de classe Foo ne sait rien du paramètre de modèle T. Et si, pour un modèle de classe, les définitions des méthodes varient en fonction des parameters du modèle? Votre règle n ° 2 ne fonctionne tout simplement pas ici.

Autre exemple: que se passe-t-il si le fichier source correspondant est énorme, long de mille lignes ou plus? Parfois, il est logique de fournir la mise en œuvre dans plusieurs fichiers source. Certaines normes vont à l’extrême de dicter une fonction par fichier (opinion personnelle: Yech!).

À l’autre extrême d’un fichier source long de plus de 1000 lignes, on trouve une classe qui ne contient aucun fichier source. Toute l’implémentation est dans l’en-tête. Il y a beaucoup à dire sur les implémentations en-tête uniquement. Si rien d’autre, cela simplifie, parfois de manière significative, le problème de liaison.