web-dev-qa-db-fra.com

Que sont les déclarations en aval en C ++?

À: 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 <iostream>

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 qu'est "add" lors de la compilation de main.cpp. Comme mentionné précédemment, écrire des déclarations pour chaque fonction que vous voulez utiliser et qui vit dans un autre fichier peut devenir fastidieux.

Pouvez-vous expliquer " déclaration avancée " plus loin? Quel est le problème si nous l'utilisons dans la fonction main()?

196
Simplicity

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

Le compilateur veut s'assurer que vous n'avez pas fait de fautes d'orthographe ni passé le mauvais nombre d'arguments à la fonction. Donc, il insiste sur le fait qu'il voit d'abord une déclaration 'add' (ou tout autre type, classe ou fonction) avant de l'utiliser.

Cela permet au compilateur de mieux valider le code et d’organiser des tâches inutiles afin de produire un fichier d’objet soigné. Si vous n'aviez pas à transmettre les objets déclarés, le compilateur produirait un fichier objet qui devrait contenir des informations sur toutes les suppositions possibles quant à ce que pourrait être la fonction 'add'. Et l'éditeur de liens devrait contenir une logique très astucieuse pour essayer de déterminer quel "ajouter" vous avez réellement l'intention d'appeler, lorsque la fonction "ajouter" peut résider dans un autre fichier objet que l'éditeur de liens rejoint avec celui qui utilise add pour produire une dll ou un exe. Il est possible que l'éditeur de liens obtienne le mauvais ajout. Supposons que vous vouliez utiliser int add (int a, float b), mais que vous ayez oublié de l'écrire par inadvertance, mais l'éditeur de liens a trouvé un int add déjà existant (int a, int b) et a pensé que c'était le bon choix et l'a utilisé à la place. Votre code serait compilé, 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 de l’utiliser.

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 ne donne que suffisamment de code pour montrer à quoi ressemble quelque chose. 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 requis. Pour une définition, vous avez besoin de la déclaration, puis du code de la fonction.

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 courant .cpp ou .h 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 .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 #includes pour aussi. Soudainement, le compilateur a #inclus des pages et des pages de code qu'il a besoin de compiler même lorsque vous ne voulez utiliser qu'une ou deux fonctions. Pour éviter cela, vous pouvez utiliser une déclaration forward et taper simplement la déclaration de la fonction vous-même en haut du fichier. Si vous n'utilisez que quelques fonctions, cela peut vraiment rendre vos compilations plus rapides par rapport à toujours inclure # l'en-tête. Pour les très gros projets, la différence peut être une heure ou plus de temps de compilation, ramené à quelques minutes.

Rompre les références cycliques lorsque deux définitions s’utilisent toutes les deux

De plus, les déclarations en aval peuvent vous aider à rompre les cycles. C'est là que deux fonctions tentent de s'utiliser l'une l'autre. Lorsque cela se produit (et que c'est une chose parfaitement valide), 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 .... qui inclut ensuite l'autre en-tête. , ce qui inclut celui que vous écrivez. Vous êtes coincé dans une situation de poule et d'oeuf avec chaque fichier en-tête essayant de réintégrer l'autre. Pour résoudre ce problème, vous pouvez déclarer les parties dont vous avez besoin dans un des fichiers et laisser le #include en dehors de ce fichier.

Par exemple:

Fichier Car.h

#include "Wheel.h"  // Include Wheel's definition so it can be used in Car.
#include <vector>

class Car
{
    std::vector<Wheel> wheels;
};

Fichier Wheel.h

Hmm ... la déclaration de Car est requise ici car Wheel a un pointeur sur une voiture, mais Car.h ne peut pas être inclus ici car cela entraînerait une erreur du compilateur. Si Car.h était inclus, cela essayerait alors d'inclure Wheel.h, ce qui inclurait Car.h, ce qui inclurait Wheel.h et cela durerait éternellement. Le compilateur génère donc une erreur. La solution est d’envoyer déclarer Car à la place:

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

Si la classe Wheel disposait de méthodes appelant des méthodes car, celles-ci pourraient être définies dans Wheel.cpp et Wheel.cpp peut désormais inclure Car.h sans provoquer de cycle.

357
Scott Langham

Le compilateur recherche pour chaque symbole utilisé dans l'unité de traduction en cours est précédemment déclaré ou non dans l'unité en cours. C'est juste une question de style: fournir toutes les signatures de méthode au début d'un fichier source, les définitions étant fournies ultérieurement. Son 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 des déclarations en avant dans les classes lorsque cela est possible. Si votre programme n'a que des fonctions (avec des fichiers en-tête ho), fournir des prototypes au début est simplement une question de style. Ce serait de toute façon le cas si le fichier d'en-tête était présent dans un programme normal avec un en-tête qui ne comporte que des fonctions.

25
Mahesh

C++ étant analysé de haut en bas, le compilateur doit être au courant de certaines choses avant de les utiliser. Alors, quand vous référencez:

int add( int x, int y )

dans la fonction principale, le compilateur doit savoir qu'il existe. Pour le prouver, essayez de le placer sous la fonction principale et vous obtiendrez une erreur du compilateur.

Donc, un 'Déclaration en aval' est exactement ce qu'il dit sur l'étain. C'est déclarer quelque chose avant son utilisation.

En règle générale, vous incluez des déclarations en aval dans un fichier d'en-tête, puis vous l'incluez de la même manière que iostream est inclus.

12
Nick

Le terme " déclaration avancée " en C++ est principalement utilisé pour déclarations de classe . Voir (la fin de) cette réponse pourquoi une "déclaration aval" d'une classe est en réalité une simple déclaration de classe avec un nom de fantaisie.

En d'autres termes, le "forward" ajoute simplement du ballast au terme, car toute déclaration peut être vue comme étant en avant dans la mesure où elle déclare un identificateur avant , il est utilisé.

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

11
sbi

Lorsque le compilateur voit add(3, 4), il doit savoir ce que cela signifie. Avec la déclaration forward, vous dites en gros au compilateur que add est une fonction qui prend deux ints et renvoie un int. C'est une information importante pour le compilateur car il a besoin de mettre 4 et 5 dans la bonne représentation sur la pile et de savoir quel type de chose est renvoyé par add.

A ce moment-là, le compilateur ne s'inquiète pas de la mise en œuvre réelle de add, c'est-à-dire où elle se trouve (ou si est même un) et si elle compile . Cela apparaîtra plus tard, after compiler les fichiers source lorsque l'éditeur de liens est appelé.

2
René Nyffenegger
int add(int x, int y); // forward declaration using function prototype

Pouvez-vous expliquer plus loin la "déclaration anticipée"? Quel est le problème si nous l'utilisons dans la fonction main ()?

C'est pareil que #include"add.h". Si vous le savez, 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 en aval".

Je suppose que add.h a cette ligne:

int add(int x, int y); 
1
Nawaz

un additif rapide concernant: vous mettez généralement ces références 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 réellement déclarée dans un fichier externe (peut également être une bibliothèque, etc.). votre main.c ressemblerait à ceci:

 # include 
 # include "add.h" 
 
 int main () 
 {
. 
 . 
. 
 
1
jack

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 faux. Un autre problème est que le compilateur ne sait pas quel type d'argument votre fonction attend et ne peut pas vous avertir si vous transmettez des valeurs du mauvais type. Il existe des règles spéciales de "promotion", qui s'appliquent lors du passage, par exemple, des valeurs à virgule flottante à une fonction non déclarée (le compilateur doit les élargir pour taper deux), ce qui n'est souvent pas ce que la fonction attend réellement, ce qui conduit à des bogues difficiles à trouver lors de l'exécution.

0
Dirk