Que sont les déclarations avancées en C ++?

Sur: http://www.learncpp.com/cpp-tutorial/19-header-files/

Ce qui suit est mentionné:

add.cpp:

int add(int x, int y) { return x + y; } 

main.cpp:

 #include  int add(int x, int y); // forward declaration using function prototype int main() { using namespace std; cout << "The sum of 3 and 4 is " << add(3, 4) << endl; return 0; } 

Nous avons utilisé une déclaration forward pour que le compilateur sache ce que ” add ” était lors de la compilation de main.cpp . Comme mentionné précédemment, l’écriture de déclarations anticipées pour chaque fonction que vous souhaitez utiliser, qui vit dans un autre fichier, peut devenir fastidieuse.

Pouvez-vous expliquer “la déclaration préalable ” plus loin? Quel est le problème si on l’utilise dans la fonction main() ?

Pourquoi forward-declare est nécessaire en C ++

Le compilateur veut s’assurer que vous n’avez pas fait de fautes d’orthographe ou que vous n’avez pas transmis le nombre d’arguments incorrect à la fonction. Ainsi, il insiste sur le fait qu’il voit au préalable une déclaration «add» (ou tout autre type, classe ou fonction) avant son utilisation.

Cela permet simplement au compilateur de mieux valider le code, et lui permet de ranger les bouts de bout en bout pour qu’il puisse produire un fichier object soigné. Si vous n’aviez pas à transmettre de déclaration, le compilateur produirait un fichier object qui devrait contenir des informations sur toutes les suppositions possibles quant à la nature de la fonction “Ajouter”. Et l’éditeur de liens devrait contenir une logique très astucieuse pour essayer de déterminer quel «ajout» vous avez réellement l’intention d’appeler, lorsque la fonction «Ajouter» peut vivre dans un fichier object différent que l’éditeur de liens utilise. un dll ou un exe. Il est possible que l’éditeur de liens ne reçoive pas le bon add. Supposons que vous vouliez utiliser int add (int a, float b), mais vous avez oublié de l’écrire par inadvertance, mais l’éditeur de liens a trouvé un int add existant (int a, int b). Votre code comstackrait, mais ne ferait pas ce que vous attendiez.

Donc, juste pour garder les choses explicites et éviter les devinettes, etc., le compilateur insiste pour que vous déclariez tout avant son utilisation.

Différence entre déclaration et définition

En passant, il est important de connaître la différence entre une déclaration et une définition. Une déclaration donne juste assez de code pour montrer à quoi ressemble quelque chose, donc pour une fonction, il s’agit du type de retour, de la convention d’appel, du nom de la méthode, des arguments et de leurs types. Mais le code de la méthode n’est pas requirejs. Pour une définition, vous avez besoin de la déclaration puis du code de la fonction également.

Comment les déclarations anticipées peuvent réduire considérablement les temps de construction

Vous pouvez obtenir la déclaration d’une fonction dans votre fichier .cpp ou .h actuel par # en incluant l’en-tête qui contient déjà une déclaration de la fonction. Cependant, cela peut ralentir votre compilation, surtout si vous #incluez un en-tête dans un fichier .h au lieu de .cpp de votre programme, car tout ce qui inclut # le .h que vous écrivez finirait par inclure tous les en-têtes. vous avez écrit #include pour aussi. Soudain, le compilateur a # inclus des pages et des pages de code qu’il doit comstackr même si vous ne vouliez utiliser qu’une ou deux fonctions. Pour éviter cela, vous pouvez utiliser une déclaration anticipée et simplement saisir la déclaration de la fonction en haut du fichier. Si vous n’utilisez que quelques fonctions, cela peut rendre vos compilations plus rapides que si vous incluiez toujours l’en-tête. Pour des projets vraiment importants, la différence pourrait être une heure ou plus de temps de compilation, ramenée à quelques minutes.

Casser les références cycliques où deux définitions s’utilisent l’une l’autre

De plus, les déclarations anticipées peuvent vous aider à casser des cycles. C’est là que deux fonctions essaient toutes deux de s’utiliser. Lorsque cela se produit (et c’est une chose parfaitement valable à faire), vous pouvez inclure un fichier d’en-tête, mais ce fichier d’en-tête essaie d’inclure le fichier d’en-tête que vous écrivez actuellement. , ce qui inclut celui que vous écrivez. Vous êtes coincé dans une situation de poulet et d’oeufs avec chaque fichier d’en-tête essayant de ré-inclure l’autre. Pour résoudre ce problème, vous pouvez effectuer une déclaration anticipée des pièces dont vous avez besoin dans l’un des fichiers et laisser le #include hors de ce fichier.

Par exemple:

Fichier Car.h

 #include "Wheel.h" // Include Wheel's definition so it can be used in Car. #include  class Car { std::vector wheels; }; 

File Wheel.h

Hmm … la déclaration de Car est requirejse ici car Wheel a un pointeur sur une voiture, mais Car.h ne peut pas être inclus ici car cela provoquerait une erreur de compilation. Si Car.h était inclus, cela essaierait alors d’inclure Wheel.h, qui inclurait Car.h, qui inclurait Wheel.h, et cela durerait éternellement. Le compilateur soulève donc une erreur. La solution consiste à déclarer la voiture à la place à la place:

 class Car; // forward declaration class Wheel { Car* car; }; 

Si class Wheel avait des méthodes qui ont besoin d’appeler des méthodes de voiture, ces méthodes pourraient être définies dans Wheel.cpp et Wheel.cpp est maintenant capable d’inclure Car.h sans provoquer de cycle.

Le compilateur recherche chaque symbole utilisé dans l’unité de traduction en cours est précédemment déclaré ou non dans l’unité en cours. Il s’agit simplement d’une question de style fournissant toutes les signatures de méthodes au début d’un fichier source, tandis que les définitions sont fournies ultérieurement. L’utilisation significative est lorsque vous utilisez un pointeur sur une classe en tant que variable membre d’une autre classe.

 //foo.h class bar; // This is useful class foo { bar* obj; // Pointer or even a reference. }; // foo.cpp #include "bar.h" #include "foo.h" 

Donc, utilisez les déclarations à l’avance dans les classes quand cela est possible. Si votre programme ne contient que des fonctions (avec des fichiers d’en-tête), fournir des prototypes au début est une question de style. Ce serait le cas si le fichier d’en-tête était présent dans un programme normal avec un en-tête qui n’a que des fonctions.

Étant donné que C ++ est analysé de haut en bas, le compilateur doit connaître les éléments avant leur utilisation. Donc, quand vous faites référence:

 int add( int x, int y ) 

dans la fonction principale, le compilateur doit savoir qu’il existe. Pour prouver cela, essayez de le déplacer en dessous de la fonction principale et vous obtiendrez une erreur de compilation.

Donc, une « déclaration anticipée » est exactement ce qu’elle dit sur l’étain. Il déclare quelque chose avant son utilisation.

En règle générale, vous incluriez les déclarations avancées dans un fichier d’en-tête, puis incluriez ce fichier d’en-tête de la même manière que iostream .

Le terme ” déclaration préalable ” en C ++ est principalement utilisé uniquement pour les déclarations de classe . Voir (à la fin de) cette réponse pour savoir pourquoi une “déclaration directe” d’une classe n’est vraiment qu’une simple déclaration de classe avec un nom sophistiqué.

En d’autres termes, le “forward” ajoute simplement du lest au terme, car toute déclaration peut être vue comme étant en avant dans la mesure où elle déclare un identifiant avant son utilisation.

(En ce qui concerne une déclaration par opposition à une définition , voir à nouveau Quelle est la différence entre une définition et une déclaration? )

Lorsque le compilateur voit add(3, 4) il doit savoir ce que cela signifie. Avec la déclaration forward, vous indiquez au compilateur que add est une fonction qui prend deux ints et retourne un int. Ceci est une information importante pour le compilateur car il doit mettre 4 et 5 dans la représentation correcte sur la stack et doit savoir de quel type est la chose renvoyée par add.

A ce moment, le compilateur ne s’inquiète pas de l’implémentation réelle de add , c’est-à-dire où il se trouve (ou s’il y en a même un) et s’il comstack. Cela apparaît plus tard, après la compilation des fichiers sources lorsque l’éditeur de liens est appelé.

 int add(int x, int y); // forward declaration using function prototype 

Pouvez-vous expliquer plus en détail la “déclaration préalable”? Quel est le problème si on l’utilise dans la fonction main ()?

C’est la même chose que #include"add.h" . Si vous savez, le préprocesseur développe le fichier que vous mentionnez dans #include , dans le fichier .cpp où vous écrivez la directive #include . Cela signifie que si vous écrivez #include"add.h" , vous obtenez la même chose, c’est comme si vous faisiez “une déclaration #include"add.h" “.

Je suppose que add.h a cette ligne:

 int add(int x, int y); 

Un problème est que le compilateur ne sait pas quel type de valeur est fourni par votre fonction. Cela suppose que la fonction retourne un int dans ce cas, mais cela peut être aussi correct que cela peut être faux. Un autre problème est que le compilateur ne sait pas quels types d’arguments votre fonction attend et ne peut pas vous avertir si vous transmettez des valeurs du type incorrect. Il y a des règles spéciales de “promotion”, qui s’appliquent lors du passage, disons des valeurs à virgule flottante à une fonction non déclarée (le compilateur doit les élargir pour saisir du double), ce qui n’est souvent pas le cas. lors de l’exécution.

un addendum rapide concernant: généralement, vous mettez ces références en avant dans un fichier d’en-tête appartenant au fichier .c (pp) où la fonction / variable etc. est implémentée. dans votre exemple, cela ressemblerait à ceci: add.h:

  extern int add (int a, int b);

Le mot clé extern indique que la fonction est effectivement déclarée dans un fichier externe (peut également être une bibliothèque, etc.). votre main.c ressemblerait à ceci:

 #comprendre 
 #include "add.h"

 int main()
 {
 .
 .
 .