J'ai un soucis pour appeler une fonction qui j'initialise dans le fichier Function.h. Je n'arrive pas à l'appeler depuis le test.cpp.
Et à la compilation, je me retrouve avec :
test.cpp: In function 'int main()':
test.cpp:8:20: error: 'dire' was not declared in this scope
dire("Salut !!");
^ Merci de bien vouloir m'aider car je bloque dessus depuis 6 jours sans solution.
Merci d'avance
- Edité par Rushex il y a moins de 30s
Bon, la fonction n'est pas "initialisée", mais
déclarée dans la définition de la classe (fichier .h)
définie dans le fichier .cpp
En l'occurrence il s'agit
de la fonction qui s'appelle "Function::dire", et pas "dire". Donc le compilateur a parfaitement raison de signaler qu'il ne trouve pas de fonction dire.
d'une fonction membre qui n'a pas été déclarée static, ce qui veut dire qu'elle devrait être invoquée sur une instance de Function.
Et bien tu créés un objet de type Function ( /!\ le constructeur doit être défini !) et tu appelles la fonction membre 'dire' sur cet objet. Ou comme Michel te l'a dit tu la mets en static.
et bien suivre l'exemple que Michel te propose --> Changer le nom de la fonction "dire" en "fonction::dire" dans le main, et déclarer la fonction "dire" en static dans l'objet "fonction" du fichier h.
michelbillaud a écrit:
#include <iostream>
class Foo {
public:
static void bar();
};
void Foo::bar() {
std::cout << "Hello\n";
}
int main() {
Foo::bar();
}
(*) c'est là où on voit si le cours de C++ qu'on suit a été mis à jour après 1990, date de l'introduction des namespaces dans le langage, avant même le premier standard C++ (98).
- Edité par michelbillaud 15 octobre 2021 à 17:41:13
Function.cpp:9:28: error: cannot declare member function 'static void Function::dire()' to have static linkage [-fpermissive] static void Function::dire() { ^
Function.cpp:9:28: error: cannot declare member function 'static void Function::dire()' to have static linkage [-fpermissive] static void Function::dire() { ^
Tu comprendras en lisant avec application.
1. le message te cause de "static"
2. Il te parle en effet de la ligne 9 où tu essaie de définir ta fonction avec le mot clé static
3. dans les exemples, où as-tu vu le mot clé static dans une définition de fonction ?
Est-ce bien la peine que tu poses des questions si tu ne tiens pas compte des réponses qu'on te donne ?
Commence par choisir ton camp :
soit tu fais des fonctions membres statiques dans une classe
soit tu définis un namespace.
mais pas de mélange. En programmation, on ne peut pas se permettre de faire n'importe quoi "en changeant plein de trucs". Je dis pas ça pour être désagréable : il y a des jours comme ça, où on fait n'importe quoi. Ca arrive. Mais si on veut que ça marche, il faut savoir ce qu'on fait.
PS: en plus tu as deux main.
- Edité par michelbillaud 15 octobre 2021 à 18:11:38
Mais quand je les déclare en static j'ai la même erreur que tout à l'heure...
Function.cpp:9:28: error: cannot declare member function 'static void Function::dire()' to have static linkage [-fpermissive] static void Function::dire() {
J'ai essayé de les déclarer static void, puis void static, puis static tout seul mais j'ai toujours la même erreur...
Et que veut tu dire de l'utilisation de #ifdef ?
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
Il est courant de mettre au début des fichiers header (*.H ou *.HPP) un entête comme suit :
#ifndef <nom du fichier>
#define <nom du fichier>
...
#endif
L'objectif est d'empeché d'inclure 2 fois ton fichier header.
Oui, bien sur qu'il est assez courant de faire cela...
Mais si tu relis correctement le code fourni par Rushex, tu verras que ce n'est pas ce qu'il a fait
Et son utilisation de #ifdef est -- effectivement -- plutot exotique pour la cause
- Edité par koala01 17 octobre 2021 à 13:50:55
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
... Mais si tu relis correctement le code fourni par Rushex, tu verras que ce n'est pas ce qu'il a fait ...
En effet, le code de Rushex est correct, en effet ce n'est pas ce qu'il fait, ... Mais que veut-il faire avec ces macros ? Pour moi ces macros ne servent à rien. Aussi, je me permets de lui expliqué ce qu'il est courant de faire et pourquoi on le fait.
Si son objectif est de définir la macro "DEF_FUNCTION", il serait bon d'y ajouté une valeur.
À quoi sert le test "#ifdef" qui suit ? C'est toujours vrai ! (Je ne pense pas que Rushex soit déjà entrain de prévoir du code multi-compilateur / multi-processeurs / multi-OS et autres subtilités qui demande de "jouer" avec des macros)
Donc pour moi, il me semble que c'est une erreur de codage de notre ami Rushex.
nom, mais, a priori, DEF_FUNCTION serait simplement le "include guard" (garde anti inclusion multiple)... sauf que ce n'est pas comme cela qu'il faut s'y prendre (il faut le test en premier, la définition du symbole et le contenu du fichier à l'intérieur du test et, bien sur, ne pas oublier le endif )
- Edité par koala01 18 octobre 2021 à 3:27:03
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
J'ai bien déclaré ma fonctions static dans la classe mais pas dans la définition.
Je n'ai plus d'erreur aux niveau de Function.cpp et Function.h, mais j'en ai une (je crois que c'est la dernière) au niveau de test.cpp où j'appelle la fonction... L'erreur :
C:\Users\patri\AppData\Local\Temp\ccH74AYe.o:test.cpp:(.text+0xc): undefined reference to `Function::dire()' collect2.exe: error: ld returned 1 exit status
- dans ton code tu a copié de travers un exemple trouvé quelque part
- pour comprendre ce qu'il faut faire et pourquoi, tu trouveras les explications sans peine google: c++ inclusion multiple.
> sûrement une erreur de codage
Non. Probablemnt une erreur de compilation séparee/ édition des liens, mais si tu ne nous fait pas voir le code en entier et les commandes de compilation que tu as utilisées, on ne peut pas être catégorique. On fait souvent les mêmes erreurs (ce qui permet de deviner la plupart du temps), mais les débutants ont une certaine ingéniosité pour se mettre dans des situations inédites, parce que totalement wtf.
C'est pour ça qu'on demande à voir TOUS les détails.
- Edité par michelbillaud 18 octobre 2021 à 9:36:53
Le C++ n'est pas du Java, on peut faire des fonctions libres. Ça n'a absolument aucun sens de faire une classe pour stocker que des fonctions statiques. Utilise un namespace pour ça à la place.
Probablement une erreur de compilation séparée/ édition des liens, mais si tu ne nous fait pas voir le code en entier et les commandes de compilation que tu as utilisées, on ne peut pas être catégorique.
personnellement, j'aurais déclaré void dire(), mais bon
- Edité par michelbillaud 20 octobre 2021 à 17:44:02
1- une seule implémentation de fonction par projet
Tout d'abord parce que tu ne doit avoir qu'une et une seule implémentation de n'importe quelle fonction.
Tu donnes une implémentation pour main dans test.cpp (ok, pas de problème) et une autre dans function.cpp. Mais ca en fait une de trop du coup. Et, pour le coup, c'est celle qui se trouve dans function.cpp qui ... n'est pas à sa place.
Et c'est d'autant pire que tu donnes deux versions totalement différentes pour la fonction main:
une version qui ne fait strictement rien et qui renvoie directement 0 (dans function.cpp) et
une version qui fait appel à la fonction Fonction::main() avant de renvoyer 0 (dans test.cpp)
Tu admettras qu'il y a de quoi rendre chevre le compilateur qui, du coup, ne sait pas déterminer quelle est la bonne version de main; celle qu'il doit utiliser pour permettre au système d'exploitation d'y accéder.
Pour comprendre ce premier point, il faudra attendre d'arriver au point 4 (processus de compilation) de ce qui promet déjà d'être un véritable roman .
2-la "signature" des fonctions
Toute déclaration ou implémentation de fonction doit SYSTEMATIQUEMENT indiquer quel sera le type de la donnée qu'elle va renvoyer, quitte, si la fonction ne renvoie rien, à indiquer le type void qui indique l'absence de donnée renvoyée.
La forme "normale" pour la déclaration d'une fonction (dans un fichier d'en-tête, par exemple) ressemblera donc à quelque chose comme type_de_retour nom_de_la_fonction(liste_des_parmètres); et la forme normale pour l'implémentation d'une fonction ressemblera à quelque chose comme
type_de_retour nom_pleinement_qualifié (liste_des_paramètres){
ce_qu'il_faut faire
}
où
type_de_retour représente le type de la donnée qui sera renvoyée (void, dans le cas présent),
nom_de_la_fonction et nom_pleinement_qualifié représentent le nom qui permettra de faire appel à la fonction (précédé, pour l'implémentation, par le nom de la classe à laquelle la fonction appartient)
liste_des_paramètres fournit la liste des types (et des noms pour l'implémentation) des données qui devront être transmises à la fonction pour qu'elle puisse fonctionner (s'il y en a, bien sur)
Les seules fonctions à faire exception à la règle étant les constructeurs -- qui prennent "simplement" le nom de la classe et une liste éventuelle de paramètre --et les destructeurs -- qui prennent le nom de la classe précédé du symbole ~ et aucun paramètre -- de classe.
Au final, tu devrais donc avoir un code ressemblant à
class Function{
public:
static void dire();
/* ^
|
ca, c'est le type de retour
*/
};
pour ton fichier d'en-tête (Function.h) et ressemblant à
/*
type de retour
| nom pleinement
| qualifié (nom_classe:: nom_fonction)
| | */
void Function::dire(){
/* ce qui doit être fait */
}
pour ton fichier d'implémentation (Function.cpp)
NOTA: il y a effectivement une exception à la règle, qui indique qu'une signature peut ne pas indiquer de type de retour, et que celui-ci sera alors "par défaut" de type int, mais, je te déconseille vraiment d'y avoir recours, car c'est une prescription maintenue pour des raisons de compatibilité avec le C, et qu'elle pourrait être supprimée à tout moment
les directives préprocesseur
Toutes les lignes de code qui commencent par le symbole # sont des lignes très spéciales, car elles sont destinées à être prises en charge par un outil appelé "préprocesseur".
Le préprocesseur est un outil "très basique" qui est capable d'obéir à quelques instructions "simple" comme
#include <nom_de_fichier>, qui va simplement remplacer cette instruction par le contenu du fichier dont le nom est indiqué et qui pourra appliquer cette instruction au contenu du fichier qu'il a utilisé, si elle est présente dedans
#define NOM_DE_SYMBOLE qui lui demande de définir (qui l'eut crut :D) le symbole nommé NOM_DE_SYMBOLE en lui donnant éventuellement une valeur par laquelle NOM_DE_SYBOLE devra être remplacé à chaque fois qu'il sera rencontré dans le code
#ifdef NOM_DE_SYMBOLE qui signifie littéralement If DEFined (si défini) et qui crée un test s'assurant que le compilateur connait le symbole en question. Ce test donnera un résultat vrai si le préprocesseur connait le symbole et faux si ce n'est pas le cas
#ifndef NOM_DE_SYMBOLE, qui signifie littéralement If Not Defined (si non défini) et qui crée un test s'assurant que le préprocesseur ne connait pas ( "Not Defined") le symbole en question. Ce test donnera vrai si le symbole n'est pas encore connu et faux si le symbole est connu
#endif qui représente la fin d'un bloc dont le début est représenté par la directive #ifdef ou par la directive #ifndef
NOTA: si un test #ifdef ou ifndef donne un résultat vrai, alors tout ce qui se trouve entre le test en question et le #endif équivalent sera gardé, par contre, si ces tests donnent un résutat faux, alors, tout ce qui se trouve entre le test et le #endif équivalent sera purement et simplement supprimé par le préprocesseur.
Le but de ces directives est -- dans l'usage que tu en fais -- d'éviter les problèmes qui pourraient apparaitre lorsque ton fichier d'en-tête fini par être -- à cause du phénomène d'inclusion en cascade (le fait que le préprocesseur va respecter les instruction #include qu'il trouve dans le contenu d'un fichier qu'il est justement occupé à inclure "quelque part") -- inclut plusieurs fois de manière directe ou indirecte (parce que C.cpp inlut C.h qui inclut B.h qui inclut A.h, on retrouve le contenu de A.h, de B.h et de C.h dans C.cpp) dans une même "unité de compilation".
Mais tu t'y prend mal car si on regarde ton fichier d'en-tête, tu commence par définir le symbole FUNCTION_H et tu demandes seulement après au préprocesseur s'il connait le symbole FUNCTION_H.
Du coup, la réponse sera forcément toujours oui, vu que tu viens justement de lui demander de le définir.
Si bien que si tu devais te trouver dans le cas de deux fichiers d'en-tête (nommons les B.h et C.h) qui incluent tous les deux function.h et d'un fichier "final" (nommons le D.cpp) qui inclut B.h et C.h, tu te retrouverais avec la classe Function qui est ... définie deux fois: une fois à cause de l'inclusion de Function.h par B.h et une fois à cause de l'inclusion de Function.h par ... C.h.
Et c'est justement cela que l'on voudrait éviter. Car notre but à nous, c'est, justement, de faire en sorte que, quel que puisse être le nombre de fois où Function.h sera inclut de manière directe ou indirecte, il n'y ait jamais qu'une seule et unique définition de notre classe Function afin de respecter une règle connue sous l'acronyme de ODR, mis pour One Definition Rule (ou, si tu préfères en francais: règle de la définition unique)
Du coup, ce que nous devons faire est justement l'inverse de ce que tu fais: on doit s'assurer de ne garder la définition de la classe (et la définition du symbole FUNCTION_H qui va avec) que ... si le symbole FUNCTION_H n'est pas encore connu.
Ce qui nous donnera un code proche de
// si le symbole FUNCTION_H n'EST PAS ENCORE CONNU
#ifndef FUNCTION_H
// ALORS
// 1- on définit FUNCTION_H
#define FUNCTION_H
// 2- on garde toutes les nouveautés, à savoir la classe
// Function
class Function{
/* tout le reste */
};
// ici se trouve la fin de ce qu'il faut garder si
// FUNCTION_H n'était pas connu à la base
#endif
NOTA: Dans le code que tu nous montre, "la chance" a voulu que Function.h ne soit au final inclut qu'une seule fois aussi bien dans Function.cpp et dans test.cpp. Mais ce serait parti "en couille" dés que ton projet aurait pris un peu d'ampleur et que tu aurais commencé à inclure le fichier Function.h dans d'autres fichiers d'en-tête.
4- processus de compilation
Il faut en effet savoir que quand on dit "je compile l'ensemble de mon projet" (ou toute phrase qui ressemble plus ou moins à cela), on fait en réalité un abus de langage en utilisant le terme "compiler" pour désigner le fait que l'on essaye de générer le résultat final (l'exécutable, dans le cas présent), car on lance en réalité un processus qui va en réalité être composé de deux étapes distinctes:
La compilation proprement dite qui consiste à "traduire" le code que tu as écrit en instructions compréhensibles par le processeur et -- accessoirement -- de générer ce que l'on appelle un "fichier objet" qui contient le résultat de la traduction et
L'édition de liens, qui va consister à regrouper l'ensemble des fichiers objets en un seul et même fichier exécutable et à faire en sorte que les appels aux différentes fonctions arrivent a "retrouver" dans l'exécutable final l'endroit où se trouve le code (binaire exécutable) de la fonction qui est appelée
Alors, une fois que l'on a conscience de ces deux étapes, il faut savoir que, si l'on ne donne pas d'indication contraire au compilateur lorsqu'on lui fourni un(e liste de) fichier(s) à compiler, il va automatiquement lancer les deux étapes l'une après l'autre.
Ainsi, la commande g++ fonction.cpp -o test va en fait demander au compilateur d'effectuer la compilation du fichier fonction.cpp et d'effectuer l'édition de liens directement après avec le résultat de la compilation qu'il aura gardé en mémoire afin de générer un programme exécutable qui sera -- dans le cas présent -- appelé test(.exe).
Ca, c'est -- étant donné la première erreur dont j'ai parlé -- sans doute la commande qui aurait eu le plus de chances de fonctionner car, la fonction main étant obligatoire pour fournir au système d'exploitation un "point d'entrée" vers le programme, l'édition de liens aurait très bien pu fonctionner.
Sauf que, comme la version de la fonction main qui aurait été utilisée à ce moment là aurait été celle ... qui ne fait strictement rien
Pour indiquer au compilateur que l'on veut qu'il n'exécute que la compilation et qu'il n'exécute surtout pas l'édition de liens (parce qu'il y a plusieurs fichiers objets à générer), il faut utiliser l'option -c, ce qui fait que, pour pouvoir compiler les fichiers foncton.cpp et test.cpp, il faut passer par trois étapes distinctes:
générer le fichier objet contenant le code de fonction.cpp avec l'instruction g++ -c fonction.cpp, qui générera automatiquement un fichier objet appelé fonction.o (*) (**)
générer le fichier objet contenant le code de test.cpp avec l'instruction g++ -c test.cpp, qui générera automatiquement un fichier objet appelé test.o (*) (**)
exécuter l'édition de liens avec l'ensemble des fichiers objet (fonction.o et test.o) afin d'obtenir l'exécutable final qui sera appelé test(.exe) avec la commande g++ fonction.o test.o -o test (**) (***)
(*) NOTA: l'ordre dans lequel tu génère les fichiers objets n'a que peu d'importance vu qu'il ne s'agit que de "fichiers temporaires", tu aurais donc tout à fait pu commencer par compiler test.cpp avant de compiler function.cpp
(**) NOTA: l'ordre dans lequel on fournit la liste des fichiers objets à utiliser lors de l'édition de liens n'a -- en théorie -- pas vraiment d'importance
5- on n'inclut à chaque fois que le stricte nécessaire, et PAS PLUS
Il y a deux choses à savoir concernant les points (3) et (4) que je viens de citer:
La première est que j'ai -- honteusement -- simplifié les choses en te disant que "la compilation proprement dite ne fait que traduire le code qu'on a écrit en instructions compréhensibles par le processeur".
C'est vrai, si l'on regarde le résultat final. Mais c'est en fait un processus beaucoup plus complexe, dont la première étape sera -- justement -- de faire travailler le préprocesseur (dont j'ai parlé au point 3) et qui utilisera le résultat obtenu par le préprocesseur pour "la suite du processus".
La deuxième, c'est que le compilateur va devoir lire et analyser chaque ligne de code laissée par le préprocesseur, et que cela prend du temps.
Or, le jeu d'inclusion en cascade va rajouter énormément de ligne de code à celui que tu as écrit. Ainsi, un code aussi "simple" que
#include <iostream>
int main(){
std::cout<<"hello world\n";
return 0;
}
se traduira, après passage du préprocesseur, en un fichier qui contient à peu près 13 000 lignes de code à cause du jeu des inclusions en cascade. Et toutes ces lignes de code devront être lues et traitées par le compilateur .
La règle générale veut donc que l'on n'inclue dans chaque fichier que ce qui est strictement et absolument nécessaire pour permettre au compilateur de travailler sur ce fichier quand il est traité "tout seul".
Ni plus, parce que cela fait du travail inutile pour le compilateur, ni moins, car sinon, il va sans doute nous envoyer paitre sous prétexte qu'il ne connait pas "quelque chose" en particulier.
Par exemple, on se rend compte que la définition de ta classe Function (celle qui se trouve dans Function.h) n'a absolument pas besoin de connaitre les fonctionnalités fournies par le fichier <iostream>; que ce fichier ne deviendra indispensable que lorsque l'on fournira l'implémentation de la fonction dire.
La directive #include <iostream> n'a donc absolument rien à faire dans le fichier Function.h. Par contre, elle devient absolument indispensable dans le fichier Function.cpp.
De la même manière, tu n'utilises pas directement les fonctionnalités fournies par les fichiers <iostream> et <string> dans ton fichier test.cpp. Tout ce que tu utilise, tout ce que le compilateur doit connaitre lorsqu'il compile la fonction main, c'est ta classe Function, et le fait qu'elle présente une fonction statique nommée dire.
Il n'a rien besoin de savoir de plus.
Il n'y a donc absolument aucune raison à à inclure ni le fichier iostream (au travers de la diretive #include <iostream>) ni le fichier <string> (au travers de la directive #include <string>) dans ton fichier test.cpp.
Et c'est d'autant plus vrai pour le fichier <string> que tu n'utilise les fonctionnalités qu'il propose absolument nulle part
Le seul fichier qu'il soit absolument indispensable d'inclure dans le fichier test.cpp, c'est le fichier Function.h
On me dira que, pour l'instant, avec seulement deux fichiers qui seront compilés au final, on ne verra pour ainsi dire aucune différence à respecter ou non cette règle générale.
Et c'est tout à fait vrai pour les "petits" projets, ceux qui ne se composent que de quelques (centaines) de fichiers. Mais je peux t'assurer que le gain deviendra très rapidement perspectible, et de plus en plus important dés que ton projet commencera à prendre de l'ampleur.
Autant directement prendre les bonnes habitudes à ce sujet
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
git is great because Linus did it, mercurial is better because he didn't.
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !
Sauf erreur, je ne me trompe jamais ! Je ferais mieux demain !