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

temps de réflexion-Pourquoi voulez-vous diviser votre dossier de toute façon?

comme le titre le suggère, le problème final que j'ai Est des erreurs de linker de définitions multiples. J'ai en fait réglé le problème, mais je n'ai pas réglé le problème de la bonne façon. Avant de commencer, je veux discuter des raisons pour diviser un fichier de classe en plusieurs fichiers. J'ai essayé de mettre tous les scénarios possibles ici - si j'en ai manqué un, s'il vous plaît Rappelez-moi et je peux faire des changements. J'espère que la sont corrects:

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 #include guards autour de ce fichier (ou #pragma une fois) pour vous assurer qu'aucun conflit ne survienne si vous #incluez le fichier dans deux fichiers d'en-tête différents qui sont ensuite inclus dans un fichier source. Vous compilez un fichier source séparé avec le implémentation de toutes les méthodes déclarées dans cette classe, car elle ne charge pas beaucoup de 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 voir, l'exemple ci-dessous pourrait être amélioré en divisant l'implémentation des méthodes de classe en 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 prévenir les erreurs de linker de définition multiple:

peut-être est-ce la principale raison pour laquelle vous avez divisé la mise en œuvre de la déclaration. Dans l'exemple ci-dessus, vous pouvez déplacer le corps de la méthode à l'extérieur de la classe. Cela le rendrait beaucoup plus propre et structuré. Cependant, selon cette question , l'exemple ci-dessus a des spécificateurs implicites inline . Le transfert de la mise en œuvre de l'intérieur de la class à l'extérieur de la classe, comme dans l'exemple ci-dessous, va vous causer des erreurs de linker, et donc vous seriez soit tout en ligne, ou déplacer les définitions de fonction à A.fichier cpp.

exemple: _l'exemple ci-dessous provoquera des "erreurs de linker à Définition multiple" si vous ne déplacez pas la définition de la fonction vers A.fichier cpp ou spécifiez la fonction inline.

// 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 (compiled, .cpp) files.
}

pour corriger 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 template, alors nous ne pouvons pas déplacer l'implémentation vers un fichier source (.fichier cpp). Ce n'est actuellement pas autorisé par (je suppose) la norme ou par compilateurs actuels. Contrairement au premier exemple de Raison 2 , ci-dessus, nous sommes autorisés à placer l'implémentation dans le fichier d'en-tête. Selon cette question la raison est que les méthodes de classe de gabarit ont également impliqué inline spécificateurs. Est-ce exact? (Il semble de bon sens.) Mais personne ne semblait savoir sur la question que je viens référencés!

donc, 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<typename T>
void some_class::class_method()
{
    // Some code
}

// Example 2: INLINE specifier used
template<typename T>
inline void some_class::class_method()
{
    // Some code
}

si vous avez une classe de modèle fichier d'en-tête, qui devient énorme en raison de toutes les fonctions que vous avez, alors je crois que vous êtes autorisé à déplacer les définitions de fonction à un autre fichier d'en-tête (généralement un .ppt fichier?) et ensuite #include file.tpp à la fin de votre fichier d'en-tête contenant la déclaration de classe. Vous ne devez pas inclure ce fichier ailleurs, cependant, d'où le .tpp plutôt que .hpp .

je suppose que vous pourriez aussi faire cela avec les méthodes en ligne d'une classe régulière? Est qui a permis aussi?

Heure Des Questions

donc j'ai fait quelques déclarations ci-dessus, dont la plupart se rapportent à la structuration des fichiers source. Je pense que tout ce que j'ai dit était correct, parce que j'ai fait quelques recherches de base et "découvert quelques trucs", mais c'est une question et donc je ne sais pas pour sûr.

ce que cela revient à, c'est comment vous organiseriez le code dans les fichiers. Je pense avoir trouvé une structure qui fonctionnera toujours.

voici ce que j'ai trouvé. (C'est "Ed Oiseau de classe du fichier de code de l'organisation / structure standard" , si vous le souhaitez. Je ne sais pas si ce sera encore très utile, c'est le but de demander.)

  • 1: déclarez la classe (modèle ou autre) dans un fichier .hpp , y compris toutes les méthodes, les fonctions d'ami et les données.
  • 2: Au bas du fichier .hpp , #include un fichier .tpp contenant la mise en œuvre de toute méthode inline . Créez le fichier .tpp et assurez-vous que toutes les méthodes sont spécifiées comme étant inline .
  • 3: tous les autres membres (fonctions non-inline, fonctions d'ami et données statiques) doivent être définis dans un fichier .cpp , qui #include s le fichier .hpp en haut pour prévenir les erreurs comme " classe ABC a pas été déclarée". Puisque tout dans ce fichier aura un lien externe, le programme se liera correctement.

Existe-t-il des normes de ce type dans l'industrie? La norme je suis venu avec le travail dans tous les cas?

22
demandé sur Community 2013-08-30 18:41:09

3 réponses

vos trois points sonnent à droite. C'est la façon habituelle de faire les choses (bien que je n'ai pas vu .l'extension du PPT avant, c'est généralement le cas .inl), bien que personnellement je viens de mettre des fonctions inline au bas des fichiers d'en-tête plutôt que dans un fichier séparé.

Voici comment j'arrange mes dossiers. J'Omets le fichier forward declare pour les classes simples.

maclasse-fwd.h

#pragma once

namespace NS
{
class MyClass;
}

maclasse.h

#pragma once
#include "headers-needed-by-header"
#include "myclass-fwd.h"

namespace NS
{
class MyClass
{
    ..
};
}

maclasse.cpp

#include "headers-needed-by-source"
#include "myclass.h"

namespace
    {
    void LocalFunc();
}

NS::MyClass::...

remplacer pragma par header guards selon la préférence..

la raison de cette approche est de réduire les dépendances d'en-tête, qui ralentissent les temps de compilation dans les grands projets. Si vous ne le saviez pas, vous pouvez transmettre déclarer 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.

s'entend d'une autre classe qui utilise la classe (prend les paramètres par pointeur / référence) doit seulement inclure l'en-tête fwd dans son propre en-tête. L'en-tête complet est alors inclus dans le fichier source de la deuxième classe. Cela réduit considérablement la quantité de déchets inutiles que vous obtenez lorsque vous tirez dans une grosse-tête, qui tire dans une autre grosse-tête, qui tire dans l'autre...

la prochaine astuce est le namespace sans nom (parfois appelé anonymous namespace). Cela ne peut apparaître que dans un fichier source et c'est comme un namespace caché seulement visible à ce fichier. Vous pouvez placer des fonctions locales, des classes etc qui ne sont utilisées que par le fichier source. Cela empêche les conflits de nom si vous créez quelque chose avec le même nom dans deux fichiers différents. (Deux fonctions locales F par exemple, peuvent donner des erreurs de linker).

4
répondu Neil Kirk 2013-08-30 15:03:28

la raison principale pour séparer l'interface de l'implémentation est que vous n'avez pas à recompiler tout votre code quand quelque chose dans l'implémentation change; vous n'avez qu'à recompiler les fichiers source qui ont changé.

comme pour" déclarer la classe (modèle ou autre)", un template est et non a class . Un template est un modèle pour créant des 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 de membre, et les fonctions de membre non-inine sont définies dans un ou plusieurs fichiers source. Les fonctions de membre Inline 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 que vous préférez.

1
répondu Pete Becker 2013-08-30 16:03:34

Existe-t-il des normes de ce type dans l'industrie?

Oui. Encore une fois, les normes de codage qui sont assez différentes de celles que vous avez exprimées peuvent aussi être trouvées dans l'industrie. Vous parlez des normes de codage, après tout, et les normes de codage gamme bon ou mauvais laid.

la norme que j'ai trouvée fonctionnera-t-elle dans tous les cas?

absolument pas. Pour exemple,

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

ici, la définition de modèle de classe Foo ne sait rien à propos de ce paramètre de modèle T. Que se passe-t-il si, pour un modèle de classe, les définitions des méthodes varient en fonction des paramètres du modèle? Votre règle n ° 2 ne fonctionne pas ici.

un autre exemple: et si le fichier source correspondant est énorme, un millier de lignes de long ou plus? Il est parfois logique de fournir la mise en œuvre en plusieurs les fichiers source. Certaines normes vont jusqu'à dicter une fonction par fichier (opinion personnelle: Yech!).

à l'autre extrémité d'un fichier source long de plus de 1000 lignes est une classe qui n'a pas de fichiers source. L'intégralité de l'implémentation est dans l'en-tête. Il y a beaucoup à dire pour les implémentations d'en-tête seulement. Si rien d'autre, il simplifie, parfois de manière significative, le problème de liaison.

0
répondu David Hammen 2013-08-30 16:25:08