Ok, je ne suis en aucun cas un expert en C/C++, mais je pensais que le but d'un fichier d'en-tête était de déclarer les fonctions, puis que le fichier C/CPP était de définir l'implémentation.
Le véritable objectif d'un fichier d'en-tête est de partager le code entre plusieurs fichiers sources. Il est communément utilisé pour séparer les déclarations des implémentations pour une meilleure gestion du code, mais ce n'est pas une obligation. Il est possible d'écrire du code qui ne dépend pas des fichiers d'en-tête, et il est possible d'écrire du code qui n'est constitué que de fichiers d'en-tête (les bibliothèques STL et Boost en sont de bons exemples). N'oubliez pas que lorsque le préprocesseur rencontre un #include
il remplace l'instruction par le contenu du fichier auquel il fait référence, puis l'instruction compilateur ne voit que le code pré-traité terminé.
Ainsi, 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();
En préprocesseur analyse séparément les fichiers Foo.cpp et Bar.cpp et produit le code suivant, que l'on peut lire sur le site Web de l'entreprise. compilateur puis l'analyse syntaxique :
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 se compile dans Bar.obj et contient une référence pour appeler dans Foo::GetNumberChannels()
. Foo.cpp se compile dans Foo.obj et contient l'implémentation réelle de Foo::GetNumberChannels()
. Après la compilation, le linker fait ensuite correspondre les fichiers .obj et les relie entre eux pour produire l'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 à l'intérieur de la déclaration de la méthode, celle-ci est implicitement déclarée comme inlined (il y a une méthode de type inline
qui peut également être utilisé explicitement). Indiquer au compilateur qu'une fonction doit être inline n'est qu'une indication qui ne garantit pas que la fonction sera effectivement inline. Mais si c'est le cas, quel que soit l'endroit d'où la fonction inline est appelée, le contenu de la fonction est copié directement dans le site d'appel, au lieu de générer un fichier de type CALL
pour entrer dans la fonction et revenir à l'appelant en sortant. Le compilateur peut alors prendre en compte le code environnant et optimiser davantage le code copié, si possible.
Cela a-t-il un rapport avec le mot-clé const ?
Non. Le const
indique simplement au compilateur que la méthode ne modifiera pas l'état de l'objet sur lequel elle est appelée au moment de l'exécution.
Quel est exactement l'avantage ou l'intérêt de procéder de cette manière plutôt que de définir l'implémentation dans le fichier CPP ?
Lorsqu'il est utilisé efficacement, il permet au compilateur de produire généralement un code machine plus rapide et mieux optimisé.