Comment un fichier d’en-tête C ++ peut-il inclure l’implémentation?

Ok, pas un expert C / C ++, mais je pensais que le but d’un fichier d’en-tête était de déclarer les fonctions, alors le fichier C / CPP devait définir l’implémentation.

Cependant, en examinant un code C ++ ce soir, je l’ai trouvé dans le fichier d’en-tête d’une classe …

public: UInt32 GetNumberChannels() const { return _numberChannels; } // <-- Huh?? private: UInt32 _numberChannels; 

Alors, pourquoi y a-t-il une implémentation dans un en-tête? Est-ce que cela a à voir avec le mot-clé const ? Est-ce que cela intègre une méthode de classe? Quel est exactement l’intérêt / le sharepoint le faire de cette façon par rapport à la définition de l’implémentation dans le fichier CPP?

Ok, pas un expert C / C ++, mais je pensais que le but d’un fichier d’en-tête était de déclarer les fonctions, alors le fichier C / CPP devait définir l’implémentation.

Le véritable objective d’un fichier d’en-tête est de partager du code entre plusieurs fichiers sources. Il est couramment utilisé pour séparer les déclarations des implémentations pour une meilleure gestion du code, mais ce n’est pas obligatoire. Il est possible d’écrire du code qui ne repose pas sur des fichiers d’en-tête, et il est possible d’écrire du code constitué uniquement de fichiers d’en-tête (les bibliothèques STL et Boost en sont de bons exemples). Rappelez-vous que lorsque le préprocesseur rencontre une instruction #include , il remplace l’instruction par le contenu du fichier référencé, puis le compilateur ne voit que le code prétraité terminé.

Donc, par exemple, si vous avez les fichiers suivants:

Foo.h:

 #ifndef FooH #define FooH class Foo { public: UInt32 GetNumberChannels() const; private: UInt32 _numberChannels; }; #endif 

Foo.cpp:

 #include "Foo.h" UInt32 Foo::GetNumberChannels() const { return _numberChannels; } 

Bar.cpp:

 #include "Foo.h" Foo f; UInt32 chans = f.GetNumberChannels(); 

Le préprocesseur parsing séparément Foo.cpp et Bar.cpp et produit le code suivant que le compilateur parsing ensuite:

Foo.cpp:

 class Foo { public: UInt32 GetNumberChannels() const; private: UInt32 _numberChannels; }; UInt32 Foo::GetNumberChannels() const { return _numberChannels; } 

Bar.cpp:

 class Foo { public: UInt32 GetNumberChannels() const; private: UInt32 _numberChannels; }; Foo f; UInt32 chans = f.GetNumberChannels(); 

Bar.cpp comstack dans Bar.obj et contient une référence à appeler dans Foo::GetNumberChannels() . Foo.cpp comstack dans Foo.obj et contient l’implémentation réelle de Foo::GetNumberChannels() . Après la compilation, l’ éditeur de liens met en correspondance les fichiers .obj et les relie pour produire le fichier exécutable final.

Alors, pourquoi y a-t-il une implémentation dans un en-tête?

En incluant l’implémentation de la méthode dans la déclaration de la méthode, celle-ci est déclarée implicitement (il existe un mot clé en inline qui peut être utilisé explicitement). Indiquer que le compilateur doit incorporer une fonction n’est qu’un indice qui ne garantit pas que la fonction sera réellement mise en ligne. Mais si c’est le cas, alors, partout où la fonction intégrée est appelée, le contenu de la fonction est copié directement sur le site d’appel, au lieu de générer une instruction CALL pour accéder à la fonction et revenir à l’appelant à la sortie. Le compilateur peut alors prendre en compte le code environnant et optimiser le code copié, si possible.

Est-ce que cela a à voir avec le mot-clé const?

Non. Le mot-clé const indique simplement au compilateur que la méthode ne modifiera pas l’état de l’object sur lequel elle est appelée lors de l’exécution.

Quel est exactement l’intérêt / le sharepoint le faire de cette façon par rapport à la définition de l’implémentation dans le fichier CPP?

Utilisé efficacement, il permet généralement au compilateur de produire un code machine plus rapide et optimisé.

Il est parfaitement valable d’avoir une implémentation d’une fonction dans un fichier d’en-tête. Le seul problème avec ceci est de briser la règle de définition unique. Autrement dit, si vous incluez l’en-tête de plusieurs autres fichiers, vous obtiendrez une erreur de compilation.

Cependant, il y a une exception. Si vous déclarez une fonction pour être en ligne, elle est exemptée de la règle à une définition. C’est ce qui se passe ici, puisque les fonctions membres définies dans une définition de classe sont implicitement intégrées.

Inline lui-même est une indication pour le compilateur qu’une fonction peut être un bon candidat pour l’inline. C’est-à-dire, élargir tout appel à la définition de la fonction, plutôt qu’un simple appel de fonction. Ceci est une optimisation qui négocie la taille du fichier généré pour un code plus rapide. Dans les compilateurs modernes, fournir cet indice pour une fonction est généralement ignoré, à l’exception des effets qu’il a sur la règle à une définition. En outre, un compilateur est toujours libre d’inclure toute fonction qu’il juge appropriée, même s’il n’a pas été déclaré en inline (explicitement ou implicitement).

Dans votre exemple, l’utilisation de const après la liste d’arguments indique que la fonction membre ne modifie pas l’object sur lequel elle est appelée. En pratique, cela signifie que l’object désigné par this , et par extension tous les membres de classe, sera considéré comme const . Autrement dit, si vous essayez de les modifier, une erreur de compilation est générée.

Il est déclaré implicitement en inline en tant que fonction membre définie dans la déclaration de classe. Cela ne signifie pas que le compilateur doit l’ inclure, mais cela signifie que vous ne violerez pas la règle de définition unique . C’est complètement sans rapport avec const * . Il n’est pas non plus lié à la longueur et à la complexité de la fonction.

S’il s’agissait d’une fonction non-membre, vous devrez alors le déclarer explicitement en inline :

 inline void foo() { std::cout << "foo!\n"; } 

* Voir ici pour plus sur const à la fin d'une fonction membre.

Même en clair C, il est possible de mettre du code dans un fichier d’en-tête. Si vous le faites, vous devez généralement le déclarer static ou plusieurs fichiers .c, y compris le même en-tête, provoqueront une erreur de “fonction à définition multiple”.

Le préprocesseur inclut textuellement un fichier d’inclusion, de sorte que le code dans un fichier d’inclusion devient une partie du fichier source (du moins du sharepoint vue du compilateur).

Les concepteurs de C ++ souhaitaient activer la programmation orientée object avec un bon masquage des données, ils s’attendaient donc à voir beaucoup de fonctions de lecture et de définition. Ils ne voulaient pas une pénalité de performance déraisonnable. Donc, ils ont conçu C ++ pour que les getters et les setters puissent non seulement être déclarés dans l’en-tête, mais aussi être implémentés. Cette fonction que vous avez montrée est un getter, et quand ce code C ++ est compilé, il n’y aura aucun appel de fonction; code pour récupérer cette valeur sera juste compilé en place.

Il est possible de créer un langage informatique dépourvu de la distinction entre fichier d’en-tête et fichier source, mais ne comprend que les «modules» réels que le compilateur comprend. (C ++ n’a pas fait cela; ils ont juste construit sur le modèle C des fichiers source et des fichiers d’en-tête inclus textuellement). Si les fichiers source sont des modules, un compilateur peut extraire le code du module, puis aligner ce code. Mais la façon dont C ++ l’a fait est plus simple à mettre en œuvre.

Pour autant que je sache, il existe deux types de méthodes, qui peuvent être implémentées en toute sécurité dans le fichier d’en-tête.

  • Méthodes inline – leur implémentation est copiée à des endroits, où elles sont utilisées, donc il n’y a pas de problème avec les erreurs de l’éditeur de liens à double définition;
  • Méthodes de modèle – elles sont en fait compilées au moment de l’instanciation du modèle (par exemple, quand quelqu’un saisit un type à la place du modèle), il n’y a donc plus aucune possibilité de problème de double définition.

Je crois que votre exemple correspond au premier cas.

Garder la mise en œuvre dans le fichier d’en-tête de classe fonctionne, car je suis sûr que vous savez si vous avez compilé votre code. Le mot-clé const garantit que vous ne modifiez aucun membre, mais que l’instance rest immuable pendant la durée de l’appel de la méthode.