Incompréhension totale SFML et principes de base

jspAmphet - Modifié le 10 janv. 2024 à 11:00
[Dal] Messages postés 6174 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 2 février 2024 - 9 janv. 2024 à 19:23

Bonjour,

Alors je ne sais pas vraiment comment expliquer le problème, il va donc y avoir des photos pour illustrer mes dires.

Je commence à me débrouiller en programmation C++. Créer des logiciel sans interface graphique devient de plus en plus simple pour moi. Dans mon apprentissage, j'ai appris que lorsqu'on écrit beaucoup de fonctions, qu'il vaut mieux arrêter de les mettre dans main.cpp et les mettre dans un fichier fonction.cpp ainsi qu'un header fonction.hpp.

Dans fonction.cpp on va venir y mettre toutes les fonctions et dans fonction.hpp on viens mettre le prototype de celles ci.OK, ensuite on va dans main.cpp et on #include "fonction.hpp" pour pouvoir utiliser les fonctions que nous avons écrites. Bon jusque-là, je n'ai jamais eu un soucis avec ça, exemple à l'appui d'un de mes premiers programmes nommé QCM où tout fonctionne parfaitement depuis le temps.

Sur le SCREENSHOT 1 et 2, on voit clairement fonction.cpp et fonction.hpp et mon programme fonctionne tel quel. J'ai énormément d'autres programmes qui fonctionnent avec cette architecture.

Mon problème

J'abandonne petit-à-petit les logiciels en mode console pour aller vers des application avec interface graphique, wooohooooo c'est beau. Dans l'absolu tout fonctionne, j'utilise la SFML pour créer mes interfaces (parce que gratuit open source et license des plus permissives c'est mon dada donc gardez pour vous les "eUh C'eSt NuL eSsAiE aUtRe ChOsE" merci).

J'apprends en ce moment à me servir des différentes instructions de la SFML pour générer des fenêtres. C'est cool, ça marche, je trouve ca simple, mais il y a un truc qui me chagrine depuis quelques jours (pour pas dire que ca me rend complètement fou... je suis très pragmatique et je m'attends en faisant deux fois la meme choses à obtenir deux fois le même résultat...).

Je crée donc une fonction qui teste si la fenêtre est ouverte pour savoir si l'utilisateur clique sur la croix pour fermer ou non. En clair, l'étape deux après avoir créé la fenêtre. J'écris tout dans main.cpp, tout fonctionne une fenêre blanche s'ouvre et quand je clique sur la croix de la fenêtre, le programme s'arrête. SUPER.

Je poursuis mon apprentissage et me rend compte que j'ai beaucoup de choses dans main.cpp qui peuvent bouger de là pour aller dans un fonction.cpp et fonction.hpp pour nettoyer mon fichier main.cpp. Donc je crée un fichier header et j'y place mes prototypes. Qu'on soit bien d'accord c'est sensé fonctionner en l'état ? Si je laisse la fonction dans main.cpp et son prototype dans header c'est bien sensé fonctionner ? Parce que ça marchait dans TOUS mes autres programmes)

Donc a partir de là, la fonction de test de la fenêtre ne fonctionne plus et le programme se met en erreur. J'ai donc été au bout de la chose et créé un fonction.cpp en plus du fonction.hpp. Je mets tout en place correctement en m'aidant de mon QCM.exe pour être sur de pas me tromper dans l'écriture des différents fichiers, et là plus rien, ne fonctionne. Mon programme ne trouve pas ma fonction et impossible de lancer SCREENSHOT 2-3-4 et 5 pour les exemple visuel.

Je comprends bien qu'il y a un problème avec ça precisément, mais je ne vois ni comment ni POURQUOI tous mes autres programmes acceptent cette architecture, pourquoi pas lui ? Surtout que quand j'écris la fonction en bas de main.cpp et que le prototype je l'écris en haut de main.cpp LÀ CA MARCHE WTF comme dirait l'autre.

Quelqu'un peut m'expliquer soit ce que je fais mal soit pourquoi c'est impossible de faire ça avec uniquement cette fonction ????????

2 réponses

[Dal] Messages postés 6174 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 2 février 2024 1 083
Modifié le 7 janv. 2024 à 21:15

Salut,

Plutôt que de poster des images, poste du code.

Ton module (tu devrais l'appeler autrement que "Main.cc" et "Main.h", par exemple "handler.cc" et "handler.h"), comprend un fichier .cc avec l'implémentation et un fichier .h avec les déclarations des fonctions et les includes nécessaires à la compilation du fichier .h et du fichier .cc.

Disons donc que ton module s'appelle "handler".

La première ligne du code .cc de ton module devrait comporter un #include "handler.h"

Tu peux aussi remettre dans le .cc les includes utilisés par l'implémentation, même s'ils sont déjà inclus avec le include du header du module.

Tu devrais lire aussi ceci :

https://websites.umich.edu/~eecs381/handouts/CppHeaderFileGuidelines.pdf

Cela va t'aider à comprendre de façon plus globale ce que tu devrais mettre dans tes fichiers .cc et tes fichiers .h.

Ensuite, pour utiliser ton module, il te faudra un fichier .cc incluant "handler.h". Par exemple, ton fichier .cc comprenant une fonction main(), que tu pourras appeler main.cc comme il se doit :-)

0
[Dal] Messages postés 6174 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 2 février 2024 1 083
9 janv. 2024 à 19:23

tes gardes dans le fichier .h doivent aussi débuter à la première ligne, et pas après les includes.

0
mamiemando Messages postés 33076 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 17 avril 2024 7 748
Modifié le 9 janv. 2024 à 03:09

Bonjour,

Préliminaires

Comme le dit fort justement Dal, partage ton code comme expliqué ici. Essaye de et de soigner l'orthographe (j'ai corrigé ton message initial). Essaye aussi d'être concis pour augmenter tes chances d'être lu et donc aidé. Comme ton message est long, ma réponse sera longue :-)

Réponse courte

À ce stade ton programme ne compile pas. Du coup, impossible de tirer des conclusions. Fais déjà en sorte que ton programme compile (pour cela il faut comprendre les messages d'erreur, et ce qui suit t'y aidera peut-être).

Ensuite, comme le dit [Dal], si tu veux des réponses plus précises (par exemple qu'on corrige tes erreurs de compilations), il faudrait nous partager le code source correctement et idéalement nous dire comment tu compiles ton projet. Beaucoup d'erreurs parmi celles affichées concernent du code qui n'est pas affiché dans tes captures d'écran.

Réponse détaillée :

Concernant ton introduction

Première point, il faut que tu comprennes comment est organisé un projet en C++.

Sous sa forme la plus simple on a un fichier ".cpp" (ou ".cc") qui contient une fonction main. Mais rapidement, ce fichier devient énorme et on fait des modules. Toi tu parles d'un module (fonction.{hpp, cpp}) mais dans un projet réaliste il y en a bien plus, et typiquement un par fenêtre. Mais souvent, un ".cpp" a besoin de fonctions/classes/... provenant d'autres modules. C'est là que les ".hpp" entre en jeu : ils exposent au reste du monde ce qui est dans le ".cpp" correspondant.

Quand on compile un projet, on commence par résoudre toutes les instructions qui commencent par un #. Le précompilateur ne sait faire que des tâches basiques (remplacer un variable définie par un #define par sa valeur, connue de fait avant la compilation ; copier coller le contenu d'un fichier dans un autre avec un #include ; etc.). C'est la précompilation.

Quand un fichier ".cpp" est précompilé, on peut le compiler. Chaque ".cpp" engendre à terme un ".o" une fois compilé. Chaque module est compilé dans un ordre arbitraire (voire ils sont compilés en parallèle). À ce stade, les ".hpp" sont juste là pour dire au compilateur qu'à terme (plus précisément, au linkage), il aura un (autre) binaire qui implémentera ces fonctions/classes externes. La production des .o correspond à la compilation.

Maintenant arrive l'heure des compte. Chaque symbole (fonction, classe) annoncé doit être implémenté une et une seule fois.

  • C'est la raison pour laquelle un symbole n'est jamais implémenté dans un ".hpp" (car sinon, il serait "dupliqué" dans tous les ".o" engendré par les ".cpp" qui utilisent ce ".hpp").
  • Dans la même veine, c'est également la raison pour laquelle on met des gardes dans les .hpp (voir exemple ci-dessous) : si c.cpp inclue a.hpp et b.hpp, et que b.cpp include b.hpp et a.hpp, tu ne veux pas que les symboles de a.hpp soient à la fois dans c.o et b.o
/* c.hpp */

#ifndef C_HPP
#define C_HPP

#include "a.hpp"
#include "b.hpp"

void c();

#endif
/* b.hpp */

#ifndef B_HPP
#define B_HPP

#include "a.hpp"

void b();

#endif
/* a.hpp */

#ifndef A_HPP
#define A_HPP

void a1();
void a2();

#endif
/* c.cpp */

void c() {
    a1();
    b();
}
  • Cela inclue tes propres symboles, mais plus généralement tous ceux que tu utilisent (dont ceux dans SFML). Et c'est pourquoi à l'heure des compte, il faut non seulement avoir tous tes ".o", mais aussi tous les ".so"/".dll" des librairies tierces que tu utilises.

Si chaque symbole est bien trouvé une et une seule fois, on peut produire un binaire. Cette étape est appelée le linkage. Le linkage produit :

  • soit un exécutable (".exe" sous windows, sans extension sous Linux),
  • soit une librairie
    • dynamique ( ".dll" sous Windows et un ".so" sous Linux)
    • statique (".lib" sous Windows, ".a" sous Linux)
  • soit un objet noyau (".ko" sous Linux)

Maintenant que nous avons vu la compilation, passons à comment organiser un projet.

Tu t'en doutes le nombre de module augmente assez rapidement dans un projet, et compiler un par un chacun de ces modules manuellement va rapidement être fastidieux. On va donc s'empresser d'automatiser tout ça.

Si ton IDE est bien configuré et que ton projet est bien défini, il suffira de cliquer sur un bouton pour que tout se fasse tout seul. Quand on redistribue son programme toutefois, on peut difficilement espérer que les autres utilisent le même environnement de développement. Donc, pour bien faire les choses, on livre le projet avec un script de compilation (typiquement un Makefile).

Cependant, le Makefile dépend du système d'exploitation et de la manière dont les libraries (par exemple SFML) ont été installées. Donc en toute rigueur, on n'écrit pas soi-même ce fichier, on le fait générer à partir d'un outil tiers (typiquement cmake) qui lui prendra en compte toutes ces considération.

Quoi qu'il en soit si tu as un Makefile, il est poli de le joindre, ne serait ce que pour qu'on sache avec quelles librairies de SFML tu linkes.

Deuxième point, un peu hors sujet, sache qu'il existe de nombreuses autres librairies graphiques libres et bien plus utilisées (par exemple GTK et Qt, et dans une moindre mesure SDL). Tu es parfaitement libre d'utiliser SFML si c'est la librairie que tu aimes et je ne chercherai pas à te faire changer d'avis, mais à mon humble avis, il est plus stratégique d'utiliser des librairies répandues (tu valoriseras plus facilement ton profil, pourra rentrer plus facilement dans des projets les utilisant, etc.).

Troisième point, tu dis que tu t'attends à ce que faire deux fois la même chose donne deux fois le même résultat. Eh bien attends toi à être déçu, car si ton programme comporte des erreurs mémoire, son comportement est le plus souvent une erreur de segmentation, mais dans le cas général, son comportement est imprévisible (en tout cas, non déterministe). En résumé, en C/C++ quand un programme plante, tu sais qu'il est faux. Quand il fonctionne il est peut être correct, et en toute rigueur, pour s'en assurer, il faut vérifier que tout est OK côté mémoire avec des outils comme valgrind.

Concernant ton problème

D'après tes captures d'écran, ton projet ne semble pas compiler. Ce qui laisse penser que des ".o" résiduels traînent et ne reflètent pas réellement l'état de ton code.

Il n'y a pas vraiment d'intérêt à déclarer dans un .h une fonction f qui est dans ton main.cpp :

  • soit f est implémentée dans main.cpp n'est utilisé que dans main.cpp, et c'est inutile (à moins qu'elle ne soit utilisée dans une fonction g déclarée avant f) ;
  • soit f est utilisée ailleurs que dans main.cpp et là tu risques d'avoir de gros problème au moment du linkage, car ça veut dire que si tu commences à avoir un main.hpp (très mauvaise idée) tu vas rapidement avoir des inclusions circulaires.

La bonne stratégie, c'est d'avoir dès le début un main.cpp minimal, et à chaque fois que le besoin s'en fait sentir, de créer un nouveau module (.hpp + .cpp). Cette étape consiste donc à "bien découper" le problème auquel ton programme s'attaque en objet cohérents. C'est là qu'arrive la programmation objet et des notions telles que l'UML.

Bonne chance

0