Différence entre l'implémentation d'une classe dans un fichier .h ou dans un fichier .cpp

StackOverflow https://stackoverflow.com/questions/1809679

  •  05-07-2019
  •  | 
  •  

Question

Je me demandais quelles sont les différences entre la déclaration et l'implémentation d'une classe uniquement dans un fichier d'en-tête, par rapport à l'approche normale dans laquelle vous protégez la classe dans l'en-tête et l'implémentez dans un fichier .cpp effectif.

Pour mieux expliquer ce dont je parle, je veux dire les différences entre une approche normale:

// File class.h
class MyClass 
{
private:
  //attributes
  public:
  void method1(...);
  void method2(...);
  ...
};

//file class.cpp
#include "class.h"

void MyClass::method1(...) 
{
  //implementation
}

void MyClass::method2(...) 
{
  //implementation
}

et une approche en-tête juste :

// File class.h
class MyClass 
{
private:
  //attributes
public:
  void method1(...) 
  {
      //implementation
  }

  void method2(...) 
  {
    //implementation
  }

  ...
};

Je peux avoir la différence principale: dans le second cas, le code est inclus dans tous les autres fichiers qui en ont besoin, générant plus d'instances de la même implémentation, donc une redondance implicite; tandis que dans le premier cas, le code est compilé par lui-même, puis chaque appel appelé objet de MyClass est lié à la mise en oeuvre dans class.cpp .

Mais y a-t-il d'autres différences? Est-il plus pratique d'utiliser une approche plutôt qu'une autre en fonction de la situation? J'ai également lu quelque part que la définition du corps d'une méthode directement dans un fichier d'en-tête est une requête implicite adressée au compilateur pour intégrer cette méthode, est-ce vrai?

Était-ce utile?

La solution

La principale différence pratique réside dans le fait que si les définitions de fonctions membres figurent dans le corps de l’en-tête, elles sont bien sûr compilées une fois pour chaque unité de traduction qui inclut cet en-tête. Lorsque votre projet contient quelques centaines ou milliers de fichiers source et que la classe en question est assez largement utilisée, cela peut signifier beaucoup de répétition. Même si chaque classe n’est utilisée que par 2 ou 3 autres personnes, plus il y a de code dans l’en-tête, plus il y a de travail à faire.

Si les définitions de fonction membre sont dans une unité de traduction (fichier .cpp) propre, elles sont compilées une fois et seules les déclarations de fonction sont compilées plusieurs fois.

Il est vrai que les fonctions membres définies (et pas seulement déclarées) dans la définition de classe sont implicitement inline . Mais inline ne signifie pas ce que les gens pourraient raisonnablement deviner. inline indique qu'il est légal de regrouper plusieurs définitions de la fonction dans différentes unités de traduction, puis de les lier. Cela est nécessaire si la classe se trouve dans un fichier d’en-tête que différents fichiers source vont utiliser. Le langage essaie donc de vous aider.

inline indique également au compilateur que la fonction pourrait utilement être insérée, mais que, malgré le nom, cela reste facultatif. Plus votre compilateur est sophistiqué, plus il est capable de prendre ses propres décisions en matière d'inline, et moins il a besoin d'indications. Plus important que la balise en ligne réelle est de savoir si la fonction est disponible pour le compilateur. Si la fonction est définie dans une unité de traduction différente, elle n'est pas disponible au moment de la compilation de l'appel. Par conséquent, si l'élément doit être intégré à l'appel, il doit s'agir de l'éditeur de liens, et non du compilateur.

Vous pourrez peut-être mieux voir les différences en envisageant une troisième façon possible de le faire:

// File class.h
class MyClass
{
    private:
        //attributes
    public:
       void method1(...);
       void method2(...);
       ...
};

inline void MyClass::method1(...)
{
     //implementation
}

inline void MyClass::method2(...)
{
     //implementation
}

Maintenant que le contenu implicite en ligne est éliminé, il reste quelques différences entre cet en-tête "Tous les en-têtes" et approche, et "en-tête plus source". approche. La manière dont vous divisez votre code entre les unités de traduction a des conséquences sur ce qui se passe au fur et à mesure de sa construction.

Autres conseils

Oui, le compilateur essaiera d’insérer une méthode déclarée directement dans le fichier d’en-tête, comme:

class A
{
 public:
   void method()
   {
   }
};

Je peux penser aux commodités suivantes pour séparer la mise en oeuvre dans les fichiers d'en-tête:

  1. Vous n'aurez pas de problème de code en raison de l'inclusion du même code dans plusieurs unités de traduction
  2. Votre temps de compilation réduira drastiquement. Rappelez-vous que pour tout modification dans le fichier d'en-tête compilateur doit construire tous les autres fichiers qui directement ou indirectement incluez-le. Je suppose que ce sera très frustrant pour quiconque de construire le toute binaire à nouveau juste pour ajouter un espace dans le fichier d'en-tête.

Toute modification d'un en-tête incluant l'implémentation forcera toutes les autres classes qui incluent cet en-tête à se recompiler et à se recréer.

Étant donné que les en-têtes changent moins souvent que les implémentations, vous pouvez gagner un temps de compilation considérable en plaçant l'implémentation dans un fichier séparé.

Comme d'autres réponses l'ont déjà indiqué, la définition d'une méthode dans le bloc class d'un fichier entraînera l'intégration du compilateur.

Oui, définir des méthodes dans la définition de classe revient à les déclarer inline . Il n'y a pas d'autre différence. Il n'y a aucun avantage à tout définir dans le fichier d'en-tête.

Quelque chose comme cela se voit habituellement en C ++ avec les classes de modèle, car les définitions de membres de modèle doivent également être incluses dans le fichier d'en-tête (du fait que la plupart des compilateurs ne prennent pas en charge export ). Toutefois, avec des classes ordinaires non modèles, cela ne sert à rien, à moins que vous ne vouliez vraiment déclarer vos méthodes comme inline .

Pour moi, la principale différence est qu'un fichier d'en-tête est comme une "interface". pour la classe, indiquer aux clients de cette classe quelles sont ses méthodes publiques (les opérations qu’elle prend en charge), sans que les clients ne s’inquiètent de la mise en œuvre spécifique de celles-ci. En un sens, c’est un moyen d’encapsuler ses clients contre les modifications d’implémentation, car seul le fichier cpp change, ce qui réduit considérablement le temps de compilation.

Une fois par le passé, j'ai créé un module protégeant des différences entre les différentes distributions CORBA et il était prévu qu'il fonctionne de manière uniforme sur diverses combinaisons de systèmes d'exploitation / compilateur / librairie CORBA. En le mettant en œuvre dans un fichier d’en-tête, il est plus facile de l’ajouter à un projet avec une simple inclusion. La même technique garantissait que le code était recompilé en même temps que le code qui l’appelait nécessitait une recompilation, c’est-à-dire qu’il était compilé avec une autre bibliothèque ou un autre système d’exploitation.

Donc, ce que je veux dire, c'est que si vous avez une bibliothèque assez petite qui devrait être réutilisable et recompilable dans différents projets, son en-tête offre des avantages en termes d'intégration avec d'autres projets par opposition à l'ajout de fichiers supplémentaires au projet principal ou à la recompilation. un fichier externe lib / obj.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top