Partage
  • Partager sur Facebook
  • Partager sur Twitter

Variables globale "multiple definition of"

25 septembre 2020 à 11:21:50

Bonjour,

j'ai un problème avec une variables globale (Initialisé dans un fichier .h et utilisé dans un .cpp). Voici le code pour plus de détails : 

le "TextureManager.h" :

#ifndef TEXTUREMANAGER_H
#define TEXTUREMANAGER_H
#include <SFML/Graphics.hpp>
#include <map>
#include <string>

sf::Texture textureTantumStanding;
sf::Texture textureTantumRunning;
sf::Texture textureTantumWaling;
sf::Texture textureTantumFlying;
sf::Texture textureTantumFalling;
sf::Texture textureTantumSleeping;

void loadAllTexture();


#endif // TEXTUREMANAGER_H

Et voici son "TextureManager.cpp" : 

#include "TextureManager.h"
#include <SFML/Graphics.hpp>
#include <string>

extern sf::Texture textureTantumStanding;
extern sf::Texture textureTantumRunning;
extern sf::Texture textureTantumWaling;
extern sf::Texture textureTantumFlying;
extern sf::Texture textureTantumFalling;
extern sf::Texture textureTantumSleeping;

Pour l'instant j'ai ce message d'erreur à la ligne 5 du .cpp : "P:\_PROGRAMMATION\Projets\Animation\src\TextureManager.cpp|5|multiple definition of `textureTantumStanding'|". 

Ce message d'erreur se répète sur chaque ligne qui suit aussi.

Merci de votre aide ;) 



  • Partager sur Facebook
  • Partager sur Twitter
25 septembre 2020 à 11:36:22

Quelle idée de mettre une variable globale dans un fichier entête !

  • Partager sur Facebook
  • Partager sur Twitter
25 septembre 2020 à 11:38:24

Salut , tu essaie de l’initialer 2 fois , tu a juste besion dans le cpp de :

bool loadAllTexture(){
    if (!textureTantumStanding.loadFromFile("texture.png"))
        return false;
    ....
    return true;
}

petit conseille crée une classe TextureManager

-
Edité par di20 25 septembre 2020 à 13:26:39

  • Partager sur Facebook
  • Partager sur Twitter
25 septembre 2020 à 11:54:20

C'est le contraire qu'il faut faire. Dans le .h on déclare, dans le .cpp on définit (une seule fois! => dans un seul .cpp) -- je fais court
  • Partager sur Facebook
  • Partager sur Twitter
C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
25 septembre 2020 à 14:08:25

Au départ je voulais faire une classe TextureManager, mais j'ai essayé de tout simplifié pour trouver d'où venais le problème.

Ensuite je voulais juste mes textures en globale pour ne pas avoir 36 passage par référence ou 36 même texture chargé dans la mémoires.

Bref je voulais ça à la base et cela correspond à ce que vous dites je crois : 

.h :

#ifndef TEXTUREMANAGER_H
#define TEXTUREMANAGER_H
#include <SFML/Graphics.hpp>

sf::Texture textureTantumStanding;
sf::Texture textureTantumRunning;
sf::Texture textureTantumWalking;
sf::Texture textureTantumFlying;
sf::Texture textureTantumFalling;
sf::Texture textureTantumSleeping;

class TextureManager
{

    public :
    static void loadAllTexture();

};


#endif // TEXTUREMANAGER_H

Et ensuite pour le .cpp :

#include "TextureManager.h"
#include <SFML/Graphics.hpp>

extern sf::Texture textureTantumStanding;
extern sf::Texture textureTantumRunning;
extern sf::Texture textureTantumWalking;
extern sf::Texture textureTantumFlying;
extern sf::Texture textureTantumFalling;
extern sf::Texture textureTantumSleeping;

void TextureManager::loadAllTexture()
{


//Initialisation des textures : textureTantumStanding.setTexture(...)


}



  • Partager sur Facebook
  • Partager sur Twitter
25 septembre 2020 à 15:14:01

Bonjour,

Les extern c'est à mettre dans le fichier .h (on dit que ça existe quelque part).
Sans les extern c'est dans le fichier .cpp (on défini une unique fois les données)

Mais le mieux est de mettre les textures dans le TextureManager, même si un Singleton n'est pas parfait, on évite d'avoir des objets globaux. D'où dans le fichier .h :

class TextureManager
{
public :
    static sf::Texture const&  Standing() {
        return  get().textureTantumStanding;
    }
    static sf::Texture const&  Running() {
        return  get().textureTantumRunning;
    }
    static sf::Texture const&  Walking() {
        return  get().textureTantumWalking;
    }
    static sf::Texture const&  Flying() {
        return  get().textureTantumFlying;
    }
    static sf::Texture const&  Falling() {
        return  get().textureTantumFalling;
    }
    static sf::Texture const&  Sleeping() {
        return  get().textureTantumSleeping;
    }
private:
    void  loadAllTexture();
    TextureManager() {
        loadAllTexture();
    }
    static TextureManager&  get() {
        static TextureManager  manager;
        return  manager;
    }
    sf::Texture  textureTantumStanding;
    sf::Texture  textureTantumRunning;
    sf::Texture  textureTantumWalking;
    sf::Texture  textureTantumFlying;
    sf::Texture  textureTantumFalling;
    sf::Texture  textureTantumSleeping;
};

Dans le .cpp, il faut juste définir la fonction void  TextureManager::loadAllStruture().

Et les textures s'utilisent par TextureManager::Walking(), ... L'initialisation des textures sera faite automatiquement au moment de la première utilisation de l'une de ces fonctions.

  • Partager sur Facebook
  • Partager sur Twitter

En recherche d'emploi.

25 septembre 2020 à 16:08:00

Bon, visiblement, il va y avoir de la place pour le progres... Essentiellement, en ce qui concerne la manière de travailler du compilateur.

Commençons donc par le commencement:

Ce que l'on appelle la "compilation" (la génération d'un programme exécutable) est en réalité composé de deux étapes majeurs:

  • La compilation proprement dite, par laquelle le compilateur va utiliser le code que tu as écrit pour générer des "fichiers objets" contenant le code binaire exécutable et
  • l'édition de liens, par laquelle tous les fichiers objets seront regroupés en un seul fichier et qui permettra aussi de faire coincider l'appel aux différentes fonctions avec l'adresse à laquelle ces fonctions se trouvent dans le programme final

(je simplifie, mais le principe est globalement correct ;) )

Ce quil faut garder en mémoire au sujet de ces deux étapes majeures, c'est

  • qu'elles sont toutes les deux exécutées par des outils différents (le compilateur d'une part et l'éditeur de liens d'autre part) et
  • que les différents outils, n'étant jamais que des programmes eux aussi, n'ont qu'une tolérance très limitée faces aux différentes erreurs qui sont commises.

Poursuivons donc en nous intéressant à la compilation:

Lors de la compilation, le compilateur va travailler sur la base de ce que l'on appelle une "unité de compilation".  Pour faire simple, il s'agit du code du fichier d'implémentation (*.cpp) ainsi que du code ajouté par tous les fichiers d'en-têtes (*.h / *.hpp) qui ont été inclus de manière directe ou indirecte.

Dans ton cas, le code

sf::Texture textureTantumStanding;
sf::Texture textureTantumRunning;
sf::Texture textureTantumWalking;
sf::Texture textureTantumFlying;
sf::Texture textureTantumFalling;
sf::Texture textureTantumSleeping;
 

qui se trouve dans un fichier d'en-tête va donc se retrouver dans ... l'ensemble des unités de compilation correspondant au fichiers d'implémentation dans lequel ton fichier d'en-tête est inclus.

Pour que tu puisse comprendre plus clairement, si texturemanager.h est inclus dans les fichier a.cpp, b.cpp et c.cpp, ces six lignes de code vont -- forcément -- se retrouver dans les fichiers objets correspondant (a.o, b.o et c.o / a.obj, b.obj et c.obj en fonction du compilateur).

Or, ces six lignes correspondent chacune à la déclaration d'une variable globale et, comme le compilateur oublie de manière systématique ce qu'il a pu faire lors du traitement d'une unité de compilation au moment de commencer à en traiter une autre, hé bien, il va -- forcément -- créer le code définissant ces six variables dans ... les trois unités de compilation correspondant aux trois fichier d'implémentation dans lesquels ton fichier d'en-tête est inclus.

Et, bien sur, après avoir généré les trois fichiers objets, le compilateur va donner la main à l'éditeur de liens.  Et les problèmes vont commencer...

Car l'éditeur de liens va, comme je te l'ai dit plus haut, se contenter de "regrouper" le contenu des différents objets, sans essayer de les modifier d'une quelconque manière.

En gros, c'est comme s'il suivait une logique proche de

  • je prend le contenu de a.o(bj)
  • je cherche le dernier caractère de ce fichier
  • j'ajoute le contenu de b.o(bj) après le dernier caractère de a.o(bj)
  • je cherche le dernier caractère de ce fichier
  • j'ajoute le contenu de c.o(bj) après le dernier caractère de b.o(bj)

Et ce n'est qu'une fois qu'il a mis tous ces fichiers ensemble qu'il va passer à la deuxième étape de son travail. En gros, et pour faire simple, il va chercher chaque accès à une variable ou à une fonction, et remplacer le nom de la variable ou de la fonction par ... "l'offet"( AKA l'adresse) à laquelle la variable ou la fonction se trouve dans son "mega fichier".

Bien sur, dans la situation que je viens de décrire, il va avoir un gros problème: s'il cherche l'une des six textures que ton code définis, il va systématiquement trouver ... trois adresses qui y correspondent.

Et du coup, il ne saura pas quelle adresse choisir.  Il n'aura donc pas d'autre choix que de t'afficher un message d'erreur (celui que tu lis dans ton EDI) avant ... d'abandonner le travail.

C'est là que le mot clé extern entre en jeu.  Ce mot clé nous permet de dire quelque chose comme

Ne perd pas ton temps à générer le code correspondant à cette variable, car je peux te garantir qu'elle existe "quelque par ailleurs" 

au compilateur.

Et le compilateur, en brave petit soldat, va suivre l'ordre à la lettre.

Seulement, pour que ce mot clé fonctionne, il faut qu'il soit placé ... dans le fichier qui sera inclus, de manière directe ou indirecte, dans l'ensemble des unités de compilation, alors que la "vraie" déclaration des variable, celle qui incitera le compilateur à effectivement générer le code pour les différentes variables globales, ne peut apparaitre que dans une et une seule unité de compilation.

Pour que ton code puisse être correct, il faut donc que le mot clé extern apparaisse dans le fichier d'en-tête sous une forme proche de

fichier .h

#ifndef TEXTUREMANAGER_H
#define TEXTUREMANAGER_H
#include <SFML/Graphics.hpp>
 
extern sf::Texture textureTantumStanding;
extern sf::Texture textureTantumRunning;
extern sf::Texture textureTantumWalking;
extern sf::Texture textureTantumFlying;
extern sf::Texture textureTantumFalling;
extern sf::Texture textureTantumSleeping;
 
class TextureManager
{
 
    public :
    static void loadAllTexture();
 
};
 
 
#endif // TEXTUREMANAGER_H

alors qu'un seul de tes fichiers d'implémetaiton contiendra la vraie déclaration de ces variables globales sous une forme proche de

fihcier .cpp

#include "TextureManager.h"
#include <SFML/Graphics.hpp>
 
sf::Texture textureTantumStanding;
sf::Texture textureTantumRunning;
sf::Texture textureTantumWalking;
sf::Texture textureTantumFlying;
sf::Texture textureTantumFalling;
sf::Texture textureTantumSleeping;
 
void TextureManager::loadAllTexture()
{
 
 
//Initialisation des textures : textureTantumStanding.setTexture(...)
 
 
}

PS: Les variables globales posent, bien souvent beaucoup plus de problèmes qu'elles ne permettent d'en résoudre, pout toute une série de raisons que la longueur de cette intervention m'incite à ne pas citer ici.

AMHA, tu devrais vraiment envisager de revoir très sérieusement ta conception, et ce, d'autant plus que les textures n'auront -- a priori -- du sens qu'au niveau de l'affichage des différents éléments de ton programme ;)

  • Partager sur Facebook
  • Partager sur Twitter
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
26 septembre 2020 à 0:26:23

Pour faire simple:

extern devant une variable est une déclaration de variable, sinon c'est une définition.

Cette déclaration est l'équivalent d'une déclaration de fonction (on peut ajouter extern devant une déclaration de fonction si ça nous chante, mais c'est implicite en quelque sorte).

Et où met on ses déclarations (fonction, classe ou variable) avec un linkage externe? Dans un header. Une déclaration n'est qu'une promesse faite au compilateur que la définition se trouvera quelque part au moment de l'édition des liens: dans une librairie partagée, ou un fichier objet ou dans la même unité de compilation, mais une et une seule fois dans la globalité de tout ce fourbi.

On peut aussi parfois mettre extern dans un cpp si on veut à partir d'une unité de compilation aller taper un symbole privé d'une librairie qui n'a pas mis cette déclaration dans les headers publics.

Où met on ses définitions? Dans un cpp. Sinon les risques de violation ODR (One Definition rule) sont grandes.

-
Edité par SpaceIn 26 septembre 2020 à 0:32:50

  • Partager sur Facebook
  • Partager sur Twitter