IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Contribuez C++ Discussion :

Pourquoi avoir des fichiers d'en-tête et des fichiers .cpp ?


Sujet :

Contribuez C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre régulier Avatar de Sachin Bhatt
    Homme Profil pro
    Collégien
    Inscrit en
    Juin 2022
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : Autre

    Informations professionnelles :
    Activité : Collégien

    Informations forums :
    Inscription : Juin 2022
    Messages : 5
    Par défaut Pourquoi avoir des fichiers d'en-tête et des fichiers .cpp ?
    c'est une question générale quelqu'un peut-il fournir des informations utiles à ce sujet
    Pourquoi C++ a-t-il des fichiers d'en-tête et des fichiers .cpp ?

  2. #2
    Expert confirmé
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 757
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 757
    Par défaut
    Je dirais qu'il y en a 3 types de fichiers : .cpp (code source), .h/ .hpp (entête) et .tpp (templates)
    Mais en réalité, le compilateur s'en cogne de l'extension/ du type.

    Si on fait 1 séparation c'est pour plusieurs raisons qui se rejoignent :
    • on peut avoir plusieurs déclarations (notamment avec les "forward declarations"), mais 1 seule définition
    • le système d'include ne fait que copier le contenu des fichiers. Donc on peut copier les déclarations mais pas les définitions (et en utilisant les "include guards")
    • le compilateur a souvent besoin des définitions pour générer le "binaire". L'exemple typique ce sont les templates.


    Édit : comme le dit @koala01, si on fait 1 séparation, c'est pour éviter des fichiers trop longs, et repartir au mieux le code pour 1 question de lisibilité et de maintenance.
    Sinon on aura 1 amalgamation

  3. #3
    Membre émérite
    Homme Profil pro
    Technicien maintenance
    Inscrit en
    Novembre 2012
    Messages
    381
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Vienne (Poitou Charente)

    Informations professionnelles :
    Activité : Technicien maintenance
    Secteur : Administration - Collectivité locale

    Informations forums :
    Inscription : Novembre 2012
    Messages : 381
    Par défaut
    Le message ci-dessus est très complet mais pour simplifier les fichiers d'en-tête servent aux déclarations et les fichiers .cpp aux définitions.

    La documentation de Microsoft explique bien la différence : Déclarations et définitions (C++)

    Par exemple, quand tu écris un programme tu as besoin d'un fichier d'en-tête, par exemple string.h qui déclare les fonctions sur les chaînes de caractères, mais elles sont définies ailleurs, tu n'as pas besoin de les réécrire dans un .cpp.

  4. #4
    Expert confirmé
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 757
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 757
    Par défaut
    Citation Envoyé par xdelatour Voir le message
    Le message ci-dessus est très complet mais pour simplifier les fichiers d'en-tête servent aux déclarations et les fichiers .cpp aux définitions.
    Et non dans les entêtes (.h/ .hpp) on définit les classes, les structures et les templates et dans le code source (.cpp) on définit les fonctions et les méthodes.

  5. #5
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Salut,

    Peut être voudrais tu comprendre les mécanismes qui se cache derrière tout cela...

    Ce qui se passe, de base, c'est que le compilateur va analyser le fichier "source" (*.cpp) dont il doit générer le code binaire exécutable de la première instruction à la dernière. Un peu à la manière dont tu lis un bon bouquin: de la première page à la dernière.

    Le fait est que, tout comme tu sais ce qui s'est passé pendant les neuf premières pages lorsque tu es à la page dix et n'a aucune idée de ce qui va se passer à la page douze, le compilateur n'a connaissance que des neuf premières instructions lorsqu'il est occupé à traiter la dixième, et n'a aucune idée de ce qui va se passer à la douzième.

    Seulement, si toi, ca ne te dérange pas de lire un événement qui ne te sera expliqué que deux pages plus loin (ou plus), le compilateur, lui, il n'est vraiment pas content lorsque tu lui parle de quelque chose qu'il ne connait pas.

    Si donc, tu lui parle, à la dixième instruction de quelque chose dont il ne prendra connaissance que lors de la douzième instruction, il va tout bonnement afficher sa colère et arrêter le traitement.

    C'est pour cela que l'on fait une distinction claire entre la "déclaration" (de types de données, de données et de fonctions) et la "définition"(ou "l'implémentation" pour une fonction: la "déclaration" consiste à dire au compilateur que "quelque chose existe" alors que la définiton consiste à "donner le contenu" de la chose.

    L'astuce étant bien sur qu'une définition permet également ... de déclarer ce qui est en cours de définition.
    Ainsi, on peut se contenter de dire que quelque chose existe avec une déclaration qui prendra une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    //on peut déclarer un type de donnée (classe ou structure)
    class MaClasse;
    // on peut déclarer une donnée de type Type
    Type donnee;
    // on peut déclarer une fonction nommée foo, renvoyant une donnée de type Type et nécessitant des arguments
    Type foo(/* liste des arguments);
    alors que la définition de ces éléments ressemblera d'avantage à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // on définit une classe ou une structure en indiquant de quoi elle est constituée
    class MaClasse{
    public:
        // on peut déclarer des fonctions qui constituent la classe
        void foo();
    private:
        // on peut déclarer les données qui la constituent
        Type donnee1;
        AutreType donnee2;
    };
    // la définition d'une donnée consiste à indiquer sa "valeur de départ"
    Type donnee{/*les valeurs utilisées pour l'initialisation};
    // la définition / l'implémentation d'une fonction consiste à indiquer ce qui doit être fait
    Type foo(/* liste de paramètres*/){
        /*on peut déclarer / définir des données */
        Type donneeLocale{/*valeurs utilsiées pour l'initialisation*/};
        /* on peut faire appel à des fonctions existantes */
        fonctionAppelee(/* paramètres*/);
        /* on peut utiliser des instructions directement comprises par le compilateur */
        return donneeLocale;
    }
    Voilà donc où l'on en est avec le compilateur.

    Le problème, maintenant, c'est qu'il ne serait vraiment pas pratique de garder toutes les déclarations et toutes les définitions dans un seul et unique fichier.

    Penses, par exemple, que certains projets sont composés de plusieurs millions de lignes de code. Si toutes ces lignes de code étaient regroupées dans un seul et unique fichier, et à condition que notre ordinateur soit en mesure de gérer un fichier aussi gros, il deviendrait pour ainsi dire impossible de se retrouver dans ce fichier et d'arriver à retrouver -- selon le cas -- la déclaration ou la définition d'une fonction qui nous intéresse, avant de devoir retrouver l'endroit du code où l'on voulait effectuer une modification.

    L'idée générale est donc de ne regrouper dans un fichier "source" (*.cpp) que la définition des fonctionnalités qui "vont bien ensembles". Par exemple, les fonctions membres d'une classe.

    Comme il est "relativement rare" d'avoir beaucoup plus d'une grosse dizaine de fonctions, et que l'on essaie généralement de ne pas dépasser "énormément" une limite "arbitraire" comprise entre 25 et 50 lignes par fonction, cela nous permet de garder des fichiers "de taille raisonnable" (mettons de manière tout à fait arbitraire entre 300 et 2 000 lignes au total).

    Le problème surviendra lorsqu'il faudra commencer à utiliser des fonctionnalités qui sont définies dans des fichiers différents, car le compilateur tient à savoir que toutes les fonctionnalités auxquelles nous avons recours existent.

    Le gros défi consiste donc à ... déclarer toutes les fonctionnalités que nous allons utiliser, sans risquer d'en oublier, et surtout, sans prendre le risque de se tromper lors de leur déclaration.

    C'est là que les fichiers d'en-tête (*.hpp) rentrent en jeu. Car si les fichiers source vont essentiellement contenir les définitions (de fonctions), il faut permettre au compilateur de savoir que "tout le reste" (comprend: essentiellement les types de données et les fonctions qui sont utilisées dans d'autre fichiers sources) existe.

    Et, bien sur, avant de pouvoir réellement manipuler un type de donnée, il faut que le compilateur sache ... quelle en est sa composition, ne serait-ce que pour qu'il puisse déterminer l'espace mémoire qui sera nécessaire pour en représenter l'intégralité.

    Nous allons donc "regrouper" les déclarations (de fonction) et définitions de type de donnée "qui vont bien ensembles" dans les fichiers d'en-tête et nous reposer sur le travail d'un outil particulier -- le préprocesseur -- pour nous assurer que toutes ces déclarations soient ajoutées "en temps utiles" -- quand il y en a besoin -- pour que le compilateur en ait connaissance.

    Dans les grandes lignes, à chaque fois que le préprocesseur va rencontrer une instruction #include <un_nom_de_fichier> il va remplacer (de manière récursive) cette instruction par ... le contenu du fichier dont le nom est donné.

    Cela devrait -- a priori -- permettre de satisfaire le compilateur

    "Toute l'astuce" consistera à ajouter "juste ce qu'il faut" de fichiers d'en-tête que pour s'assurer que toutes les fonctionnalités utilisées soient connues, tout en évitant autant que possible de rajouter ** trop ** de fonctionnalités "inutiles".

    Car il faut bien comprendre que le compilateur va devoir analyser l'intégralité de tous les fichiers inclus récursivement grâce à cette instruction #include <un_nom_de_fichier>, et que cela va -- forcément -- lui demander ... un certain temps (pour ne pas dire un temps certain).

    NOTA: Depuis C++20, une nouvelle fonctionnalité a été ajoutée au langage. Il s'agit des "modules".

    Pour faire simple, les modules vont permettre de "pré traiter" toutes les fonctionnalités qui "vont bien ensemble" et qui sont placées dans un module particulier, en permettant même de "cacher" les fonctionnalités que l'on ne souhaite pas rendre accessibles.

    Une fois qu'un module a été généré, cela devrait accélérer énormément le travail du compilateur. Mais les explications les concernant sortent du cadre de cette intervention
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [WPF] Comment avoir un fichier log des erreurs ?
    Par Flaburgan dans le forum C#
    Réponses: 5
    Dernier message: 29/04/2011, 16h36
  2. avoir des fichiers .java à partir de fichiers .class
    Par ROUGE87 dans le forum Langage
    Réponses: 20
    Dernier message: 04/03/2011, 16h20
  3. [1.x] Avoir des liens absolus vers des fichiers statiques ?
    Par nelk75 dans le forum Symfony
    Réponses: 4
    Dernier message: 09/02/2010, 15h56
  4. Réponses: 8
    Dernier message: 23/03/2009, 14h51
  5. Avoir des fichiers DICOM
    Par twix24 dans le forum Traitement d'images
    Réponses: 1
    Dernier message: 28/11/2007, 16h09

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo