Pourquoi le forward-declare est nécessaire en C++ ?
Le compilateur veut s'assurer que vous n'avez pas fait de fautes d'orthographe ou passé le mauvais nombre d'arguments à la fonction. Il insiste donc pour voir une déclaration de "add" (ou de tout autre type, classe ou fonction) avant de l'utiliser.
Cela permet simplement au compilateur de mieux valider le code et de mettre de l'ordre dans les détails afin de produire un fichier objet bien ordonné. Si vous n'aviez pas à déclarer les choses, le compilateur produirait un fichier objet qui devrait contenir des informations sur toutes les suppositions possibles sur ce que la fonction 'add' pourrait être. Et le linker devrait contenir une logique très intelligente pour essayer de déterminer quelle fonction 'add' vous avez réellement l'intention d'appeler, alors que la fonction 'add' peut se trouver dans un fichier objet différent que le linker joint à celui qui utilise add pour produire une dll ou un exe. Il est possible que l'éditeur de liens obtienne le mauvais add. Disons que vous vouliez utiliser int add(int a, float b), mais que vous avez accidentellement oublié de l'écrire, mais que le linker a trouvé un int add(int a, int b) déjà existant et a pensé que c'était le bon et l'a utilisé à la place. Votre code serait compilé, mais ne ferait pas ce que vous attendiez.
Ainsi, afin de garder les choses explicites et d'éviter les devinettes, 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 donne juste assez de code pour montrer à quoi ressemble quelque chose, donc pour une fonction, c'est le type de retour, la convocation, le nom de la méthode, les arguments et leurs types. Mais le code de la méthode n'est pas nécessaire. Pour une définition, vous avez besoin de la déclaration et du code de la fonction.
Comment les déclarations préalables 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 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 d'un .cpp de votre programme, car tout ce qui #inclut le .h que vous écrivez finira par #inclure tous les en-têtes pour lesquels vous avez écrit des #includes. Soudainement, le compilateur a #inclus des pages et des pages de code qu'il doit compiler même si vous ne vouliez utiliser qu'une ou deux fonctions. Pour éviter cela, vous pouvez utiliser une déclaration en avant et taper vous-même la déclaration de la fonction en haut du fichier. Si vous n'utilisez que quelques fonctions, cela peut vraiment accélérer la compilation par rapport à l'inclusion systématique de l'en-tête. Pour les projets de grande envergure, la différence peut représenter une heure ou plus de temps de compilation, ramenée à quelques minutes.
Rompre les références cycliques où deux définitions s'utilisent l'une l'autre
De plus, les déclarations en avant peuvent vous aider à briser les cycles. C'est le cas lorsque deux fonctions essaient de s'utiliser l'une l'autre. Lorsque cela se produit (et c'est une chose parfaitement valable), vous pouvez #inclure un fichier d'en-tête, mais ce fichier d'en-tête tente de #inclure le fichier d'en-tête que vous êtes en train d'écrire.... qui #inclut alors l'autre en-tête, qui #inclut celui que vous êtes en train d'écrire. Vous êtes coincé dans une situation de poule et d'œuf, chaque fichier d'en-tête essayant de re #inclure l'autre. Pour résoudre ce problème, vous pouvez déclarer les parties dont vous avez besoin dans l'un des fichiers et laisser le #include en dehors de ce fichier.
Eg :
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 nécessaire ici car Wheel a un pointeur sur une Car, mais Car.h ne peut pas être inclus ici car cela entraînerait une erreur de compilation. Si Car.h était inclus, il essaierait alors d'inclure Wheel.h qui inclurait Car.h qui inclurait Wheel.h et cela continuerait indéfiniment, donc le compilateur lève une erreur. La solution est de déclarer Car à la place :
class Car; // forward declaration
class Wheel
{
Car* car;
};
Si la classe Wheel a des méthodes qui doivent appeler des méthodes de Car, ces méthodes peuvent être définies dans Wheel.cpp et Wheel.cpp est maintenant capable d'inclure Car.h sans causer un cycle.