Comment un fichier d'en-tête C++ peut-il inclure une implémentation?
Ok, pas un expert C / C++ par aucun moyen, mais je pensais que le point d'un fichier d'en-tête était de déclarer les fonctions, puis le fichier c/CPP était de définir l'implémentation.
Cependant, en examinant du code C++ ce soir, j'ai trouvé ceci 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 ça a à voir avec le mot-clé const
? Le fait inline d'une méthode de classe? Quel est exactement le bénéfice/point de le faire de cette façon plutôt que de définir la mise en œuvre dans le dossier du RPC?
6 réponses
Ok, pas un expert C / C++ par aucun moyen, mais je pensais que le point d'un fichier d'en-tête était de déclarer les fonctions, puis le fichier c/CPP était de définir l'implémentation.
le véritable but d'un fichier d'en-tête est de partager du code parmi plusieurs fichiers source. Il est couramment utilisé pour séparer les déclarations des implémentations pour une meilleure gestion de code, mais ce n'est pas une exigence. Il est possible d'écrire code qui ne s'appuie pas sur les fichiers d'en-tête, et il est possible d'écrire du code qui est composé uniquement de fichiers d'en-tête (les bibliothèques STL et Boost en sont de bons exemples). Rappelez-vous, lorsque le préprocesseur rencontre une déclaration #include
, il remplace la déclaration avec le contenu du fichier référencé, puis le compilateur voit seulement le code pré-traité complété.
Ainsi, par exemple, si vous avez l' 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 analyse Foo.le rpc et le Bar.cpp séparément et produit le code suivant que le compilateur puis parses:
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 compile dans la barre.obj et contient une référence à l'appeler Foo::GetNumberChannels()
. Foo.cpp compile en Foo.objet et contient la mise en œuvre effective de Foo::GetNumberChannels()
. Après Compilation, le linker correspond alors au .obj file et les lie ensemble pour produire l'exécutable final.
alors pourquoi y a-t-il une implémentation dans un en-tête?
en incluant la méthode mise en œuvre dans la déclaration de méthode, elle est implicitement déclarée comme intimée (il existe un mot-clé inline
qui peut être explicitement utilisé). Indiquant que le compilateur doit incorporer une fonction est seulement un soupçon qui ne garantit pas que le fonctionnement réellement obtenir inline. Mais si c'est le cas, alors partout où la fonction inlined est appelée à partir, le contenu de la fonction est copié directement dans le site d'appel, au lieu de générer une déclaration CALL
pour sauter dans la fonction et revenir à l'appelant lors de la sortie. Le compilateur peut alors prendre en compte le code environnant et optimiser davantage le code copié, si possible.
est-ce que ça a à voir avec le mot-clé const?
Pas de. Le mot-clé const
indique simplement au compilateur que la méthode ne modifiera pas l'état de l'objet sur lequel elle est appelée à l'exécution.
Quel est l'avantage/l'intérêt de procéder de cette façon par rapport à la définition de la mise en oeuvre dans le fichier du RPC?
lorsqu'il est utilisé efficacement, il permet au compilateur de produire habituellement un code machine plus rapide et mieux optimisé.
il est parfaitement valide d'avoir une implémentation d'une fonction dans un fichier d'en-tête. Le seul problème est de briser la règle de la définition unique. C'est, si vous incluez l'en-tête à partir de plusieurs autres fichiers, vous obtiendrez une erreur de compilation.
il y a cependant une exception. Si vous déclarez qu'une fonction est en ligne, elle est exemptée de la règle one-definition -. C'est ce qui se passe ici, les fonctions définies à l'intérieur d'une définition de classe sont implicitement inline.
Inline lui-même est une indication pour le compilateur qu'une fonction peut être un bon candidat pour la stabilité. Qui est, l'expansion de toute l'appel à elle dans la définition de la fonction, plutôt qu'un simple appel de fonction. C'est une optimisation qui échange la taille du fichier généré pour du code plus rapide. Dans les compilateurs modernes, cette indication d'inclinaison pour une fonction est généralement ignorée, sauf pour les effets qu'elle a sur la règle one-definition -. Aussi, un compilateur est toujours libre de inline toute fonction qu'il juge appropriée, même si elle n'a pas été déclarée 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'objet sur lequel elle est appelée. En pratique , cela signifie que l'objet pointé par this
, et par extension tous les membres de la classe, sera considéré comme const
. C'est, en essayant de les modifier génère une erreur de compilation.
il est implicitement déclaré inline
en tant que fonction de membre défini dans la déclaration de classe. Cela ne signifie pas que le compilateur a pour le mettre en ligne, mais cela signifie que vous ne briserez pas la règle de définition unique . Cela n'a aucun rapport avec const
* . Il est également sans rapport avec la longueur et la complexité de la fonction.
si c'était une fonction non-membre, alors vous auriez à le déclarer explicitement comme inline
:
inline void foo() { std::cout << "foo!\n"; }
* voir ici pour plus de détails sur const
à la fin d'une fonction membre.
même en C simple, il est possible de mettre du code dans un fichier d'en-tête. Si vous le faites, vous devez habituellement le déclarer static
ou multiple .c les fichiers comprenant le même en-tête causeront une erreur "fonction définie par multiplication".
le préprocesseur inclut textuellement un fichier include, de sorte que le code dans un fichier include devient une partie du fichier source (au moins du point de vue du compilateur).
les concepteurs de C++ ont voulu activer programmation orientée objet avec bonne dissimulation des données, de sorte qu'ils s'attendaient à voir beaucoup de getter et de setter fonctions. Ils ne voulaient pas déraisonnable de performances. Ainsi, ils ont conçu C++ pour que les getters et les setters ne puissent pas seulement être déclarés dans l'en-tête mais réellement implémentés, donc ils seraient en ligne. La fonction que vous avez montrée est un getter, et quand ce code c++ est compilé, il n'y aura pas d'appel de fonction; le code pour récupérer cette valeur sera simplement compilé en place.
il est possible de faire un langage informatique qui n'a pas la distinction fichier d'en-tête/fichier source, mais qui a simplement des" modules " réels que le compilateur comprend. (C++ n'a pas fait cela; ils ont juste construit sur le dessus du modèle de C réussi des dossiers source et des dossiers d'en-tête textuellement inclus.) Si les fichiers source sont des modules, il serait possible pour un compilateur de sortir le code du module et de l'aligner. Mais la façon dont C++ l'a fait est plus simple à mettre en oeuvre.
autant que je sache, il existe deux types de méthodes, qui peuvent être mises en œuvre en toute sécurité à l'intérieur du fichier d'en-tête.
- méthodes Inline - leur mise en œuvre est copiée à des endroits, où ils sont utilisés, de sorte qu'il n'y a pas de problème avec double définition des erreurs de linker;
- Modèle de méthodes - en réalité, ils sont compilés au moment de l'instanciation d'un modèle (par exemple. quand quelqu'un entre un type à la place de template), il n'y a donc pas possibilité de problème de double définition.
je crois que votre exemple correspond au premier cas.
garder l'implémentation dans le fichier d'en-tête de classe fonctionne, comme je suis sûr que vous savez si vous avez compilé votre code. Le mot-clé const
garantit que vous ne changez aucun membre, il garde l'instance immuable pour la durée de l'appel de méthode.