Question : Variables static/méthode static/initialisation [Résolu]

Messages postés
21
Date d'inscription
mardi 11 août 2009
Dernière intervention
20 juin 2011
- - Dernière réponse : Inutqen
Messages postés
21
Date d'inscription
mardi 11 août 2009
Dernière intervention
20 juin 2011
- 11 mai 2011 à 15:45
Bonjour à tous, j'ai besoin d'un coup de main :)

J'ai un petit soucis avec un code C++, je suppose que la réponse à ma question se trouve quelque part sur le net, mais comme je ne sais pas exactement la source de l'erreur je ne sais pas où chercher.


Après avoir simplifié mon code à l'extrême, voilà ce que je veux faire :
Dans une class Class, je veux créer un conteneur static dans lequel sont stockés toutes les instances de Classe. De plus, mes instances de Classe sont créées dans une méthode static de la class Classe. Quand j'essaye d'accéder depuis une autre class à mon conteneur, les attributs de mes instances sont incohérents...

Parce qu'un code vaut mieux qu'un long discours :

Classe.h
#include <vector>
#include 

class Classe
{
public:
   Classe(int);
   static vector<Classe*> vect;
   int nombre;
   static void creerClasse();
}


Classe.cpp

#include Classe.h

std::vector<Classe*> Classe::vect;

using namespace std;

Classe::Classe(int nb)
{
   nombre = nb;

   vect.push_back(this);
}

void Classe::creerClasse()
{
   Classe cl(1);
   cout << vect.back()->nombre<<endl;
}


main.cpp

#include...

int main()
{
   Classe::creerClasse();
   cout<<Classe::vect.back()->nombre<<endl;

   return 0;
}


L'affichage de nombre dans "creerClasse" donne 1, mais l'affichage de nombre dans le main donne n'importe quoi (4603319 par exemple).

Du coup le problème est facile à contourner, mais j'aimerais savoir la cause.

Merci !
Afficher la suite 

Votre réponse

10 réponses

Meilleure réponse
Messages postés
3830
Date d'inscription
dimanche 12 décembre 2004
Dernière intervention
19 novembre 2018
3
Merci
Pour ton exemple:
Dans "creerClasse()", tu crées un objet en local temporaire, qui est détruit au sortir de la fonction.
Donc forcément, la valeur va être fausse si tu essaies de la lire après sa destruction.

Si tu avais fait un "new Class", ce souci n'aurait pas eu lieu.

________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question

Dire « Merci » 3

Quelques mots de remerciements seront grandement appréciés. Ajouter un commentaire

Codes Sources a aidé 98 internautes ce mois-ci

Commenter la réponse de cptpingu
Messages postés
181
Date d'inscription
mardi 6 avril 2010
Dernière intervention
7 janvier 2012
3
Merci
En reprenant le code de départ, et en affichant juste les variables ainsi que leurs

adresses, on voit bien que le problème vient de la portée de l'objet comme l'a dit CptPingu :

Avec le code suivant :

#include <vector>
#include 
using namespace std;
class Classe
{
public:
   Classe(int);
   static vector<Classe *> vect;
   int separateur;
   int nombre;
   static void creerClasse();
};



std::vector<Classe*> Classe::vect;

using namespace std;

Classe::Classe(int nb)
{
   nombre = nb;

    cout << "\n\tLOCALISATION : Constructeur de classe\n";

    cout << "\n[DEBUG] 'nombre' dans constructeur de Classe() :  " << nombre;
    printf("\n[DEBUG] @'nombre' dans constructeur de Classe() : 0x%08x", 

&nombre);
    printf("\n[DEBUG] @'cl' dans constructeur de Classe() : 0x%08x\n", this);
   vect.push_back(this);
}

void Classe::creerClasse()
{

    cout << "\n\tLOCALISATION : 'creerClasse()'\n";

    Classe cl(126);

    cout << "\n\tLOCALISATION : 'creerClasse()'\n";

    cout << "\n[DEBUG] 'nombre' dans creerClasse() : " << vect.back()->nombre;
    printf("\n[DEBUG] @'nombre' dans creerClasse() : 0x%08x", 

&vect.back()->nombre);
    printf("\n[DEBUG] @'cl' dans creerClasse() : 0x%08x\n", &cl);
}

int main()
{
   Classe::creerClasse();

    cout << "\n\tLOCALISATION : 'main()'\n";

    cout << "\n[DEBUG] 'nombre' dans main() : "  << Classe::vect.back()->nombre;
    printf("\n[DEBUG] @'nombre' dans main() : 0x%08x", 

&Classe::vect.back()->nombre);
    printf("\n[DEBUG] @'cl' dans main() : 0x%08x", Classe::vect.back());
    printf ("\n[DEBUG] @'cl' dans main() en decimal :  %d", Classe::vect.back());


    cout << "\n\n";
    return 0;
}



La console affiche ceci :



        LOCALISATION : 'creerClasse()'

        LOCALISATION : Constructeur de classe

[DEBUG] 'nombre' dans constructeur de Classe() :  126
[DEBUG] @'nombre' dans constructeur de Classe() : 0x0022ff3c
[DEBUG] @'cl' dans constructeur de Classe() : 0x0022ff38

        LOCALISATION : 'creerClasse()'

[DEBUG] 'nombre' dans creerClasse() : 126
[DEBUG] @'nombre' dans creerClasse() : 0x0022ff3c
[DEBUG] @'cl' dans creerClasse() : 0x0022ff38

        LOCALISATION : 'main()'

[DEBUG] 'nombre' dans main() : 2293560
[DEBUG] @'nombre' dans main() : 0x0022ff3c
[DEBUG] @'cl' dans main() : 0x0022ff38
[DEBUG] @'cl' dans main() en decimal :  2293560


Or ce qui saute aux yeux, c'est que le nombre récupéré dans le main est en fait l'adresse mémoire de la cl !
J'ai d'ailleurs rajouté une variable nommée "séparateur" juste avant la déclaration de nombre pour pouvoir distinguer l'adresse de nombre de l'adresse de la classe. Il faut à mon avis en effet faire un "new Classe" comme l'a dit CptPingu.

Donc rien d'aléatoire (ou presque) dans la valeur "nombre" qui sort dans le main()

Pop70

Dire « Merci » 3

Quelques mots de remerciements seront grandement appréciés. Ajouter un commentaire

Codes Sources a aidé 98 internautes ce mois-ci

Commenter la réponse de pop70
Messages postés
181
Date d'inscription
mardi 6 avril 2010
Dernière intervention
7 janvier 2012
0
Merci
Bonjour,
Y'a pas mal de trucs a revoir :
Pas de point virgule après la déclaration de classe, les cout et vector tout seul qui signifie qu'il doit y'avoir un using namespace quelque part avant, les membres déclarés public...
Sinon, pour ce qui est des membres statiques, s'ils sont là pour contenir les classes ou les compter, le plus simple est de les déclarer avec dans les fichiers pour y accèder, mais pas dans la classe.

Au final : (Reste juste à séparer dans différents fichier avec 2-3 #include)

#include
#include <vector>


class Classe
{
public:
Classe(int);
int getNombre();


private :
int nombre;

};

static int nombreDeClasses = 0;
static std::vector<Classe*> toutesClasses;



Classe::Classe(int nb)
{
nombre = nb;

nombreDeClasses ++;
toutesClasses.push_back(this);
}

int Classe::getNombre()
{
return nombre;
}




int main()
{
Classe maClasse(21);
Classe maClasse2(2);
Classe maClasse3(384);

std::cout << "Il y a " << nombreDeClasses << " construite(s).\n";
for (int i = 0; i < toutesClasses.size(); i++)
{
std::cout << "\nLa classe " << i << " contient : " << toutesClasses.at(i)->getNombre();
}

std::cout << "\n\n";
return 0;
}


C++dialement,

Pop70
Commenter la réponse de pop70
Messages postés
3830
Date d'inscription
dimanche 12 décembre 2004
Dernière intervention
19 novembre 2018
0
Merci
La réponse de pop70 étant très complète, je n'aurais que peu de chose à ajouter:

- Évite les "using namespace", voir: http://0217021.free.fr/portfolio/axel.berardino/articles/bon-usage-using-namespace
- Pourquoi as-tu besoin de faire cela ?
- Ne serait-ce pas le design pattern "FlyWeight" dont tu aurais besoin ?

________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
Commenter la réponse de cptpingu
Messages postés
21
Date d'inscription
mardi 11 août 2009
Dernière intervention
20 juin 2011
0
Merci
Merci pour vos réponses.

[quote="pop70"]Y'a pas mal de trucs a revoir :
Pas de point virgule après la déclaration de classe, les cout et vector tout seul qui signifie qu'il doit y'avoir un using namespace quelque part avant, les membres déclarés public.../quote
Le code écrit est un code rapidement écrit à la main, pas un copié collé, ce qui explique les oublis de namespace et de point virgule. Les membres déclarés public sont juste la pour simplifier à l'extrême, et donc ne pas écrire un getter.

[quote="pop70"]Sinon, pour ce qui est des membres statiques, s'ils sont là pour contenir les classes ou les compter, le plus simple est de les déclarer avec dans les fichiers pour y accèder, mais pas dans la classe. /quote
mmmh, mais si c'est quelque chose dont je risque d'avoir besoin dans plusieurs autres classes (dont des classes pas encore codées), ce n'est pas plus simple de le mettre dans la classe, et d'y accéder ensuite de partout (quitte à mettre le conteneur en private et de faire un getter ?

Ton exemple fonctionne, mais ma question est plus de comprendre pourquoi ce que je fais ne fonctionne pas (ou est moche) plutôt que de faire fonctionner le code (y'a pleins de solutions pour ça).

[quote="CptPingu"]Évite les "using namespace"/quote
Yep, c'est un autre problème a priori, mais merci pour le lien.

[quote="CptPingu"]Ne serait-ce pas le design pattern "FlyWeight" dont tu aurais besoin ? /quote
Je ne crois pas (voir en dessous).

[quote="CptPingu"]Pourquoi as-tu besoin de faire cela ? /quote
J'ai juste besoin d'un truc qui contient toutes les instances d'une classe. J'ai pris un vector pour l'exemple. Il me semblait que faire un membre static à l'intérieur de la classe était le moyen le plus propre (si ce n'est pas le cas je serais ravi d'apprendre pourquoi).

Le problème semble venir du fait que les appels au constructeur se font depuis une méthode static. Mais je ne comprends pas pourquoi, ni en quoi c'est moche...
Commenter la réponse de Inutqen
Messages postés
3830
Date d'inscription
dimanche 12 décembre 2004
Dernière intervention
19 novembre 2018
0
Merci
Il me semblait que faire un membre static à l'intérieur de la classe était le moyen le plus propre (si ce n'est pas le cas je serais ravi d'apprendre pourquoi).

Ça dépend des cas. On évite généralement de faire des méthodes statiques de partout. Attention, néanmoins je pense personnellement que pour ce que tu cherches à réaliser, c'est une méthode propre, et je suis d'accord avec ton approche.

J'ai peut être encore mal compris ta question. Mais si tu cherches à avoir un élément qui est capable de garder une trace de toute classe créée, alors je te propose une méthode générique et non intrusive pour le faire.
Il te suffit d'hériter de la classe GenericAutoRef, et lors d'un appel à showInstance<TaClasse>(), tu vois la liste de ces classes. (Utilisation du CRTP => Cf Google > "Curiously Recurring Template Pattern").
A toi ensuite d'adapter showInstance pour faire ce que tu veux avec les classes.

#include 
#include <list>

template <typename T>
class GenericAutoRef
{
public:
  typedef typename std::list<T*>::iterator iterator;

  GenericAutoRef() { _tab.push_back(static_cast<T*>(this));}
  ~GenericAutoRef() { _tab.remove(static_cast<T*>(this));}

  static iterator begin() { return _tab.begin(); }
  static iterator end() { return _tab.end(); }
private:
  static std::list<T*> _tab;
};

template <typename T>
std::list<T*> GenericAutoRef<T>::_tab;

class Class : public GenericAutoRef<Class>
{
public:
  Class(int i) : _i(i) {}
  virtual ~Class() {}
  int get() const { return _i;}
private:
  const int _i;
};

template <typename T>
void showInstance()
{
  std::cout << "Dump:" << std::endl;
  int nb = 0;
  for (typename GenericAutoRef<T>::iterator it = GenericAutoRef<T>::begin();
       it != GenericAutoRef<T>::end(); ++it)
  {
    std::cout << *it << std::endl;
    ++nb;
  }
  std::cout << "Nb class = " << nb << std::endl;
}

int main()
{
  showInstance<Class>();
  Class* cl1 = new Class(1);
  showInstance<Class>();
  Class* cl2 = new Class(2);
  showInstance<Class>();
  Class* cl3 = new Class(3);
  showInstance<Class>();

  {
    Class cl4(4);
    showInstance<Class>();
  }
  showInstance<Class>();

  delete cl1;
  showInstance<Class>();
  delete cl2;
  showInstance<Class>();
  delete cl3;
  showInstance<Class>();

  return 0;
}


________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
Commenter la réponse de cptpingu
Messages postés
21
Date d'inscription
mardi 11 août 2009
Dernière intervention
20 juin 2011
0
Merci
Merci, je regarde ça tout de suite.

Une idée de la raison du pourquoi du comment mon code plante ?
Commenter la réponse de Inutqen
Messages postés
21
Date d'inscription
mardi 11 août 2009
Dernière intervention
20 juin 2011
0
Merci
Merci pour le GenericAutoRef, ça fait ce je veux.

Par contre pour la raison du plantage de mon code je ne comprends toujours pas.

Si dans "creerClasse()" je fais :

Classe cl(1);
vect.push_back(&cl);

(c'est-à-dire que je met à jour le vector dans creerClasse() et pas dans le constructeur)
ça marche.

Or cl devrait être détruit aussi, non ?
Commenter la réponse de Inutqen
Messages postés
3830
Date d'inscription
dimanche 12 décembre 2004
Dernière intervention
19 novembre 2018
0
Merci

Classe cl(1);
vect.push_back(&cl);

(c'est-à-dire que je met à jour le vector dans creerClasse() et pas dans le constructeur)
ça marche.


Si tu avais fait: vect.size() tu aurais eu le bon nombre malgré que tu ais eu un élément corrompu.
Or ici, tu prends l'adresse de cl que tu ajoutes en fin de vecteur. cl est détruit et donc l'élément en fin de vecteur est un pointeur qui pointe sur un élément détruit. Cet élément du vecteur (le pointeur) est toujours dans le vecteur.
Quand tu fais back(), tu récupères cet élément, et tu essaies de regarder dedans, d'ou un nombre incorrect (d'ailleurs ça peut aussi planter, puisque l'accès à un élément désallouer est indéfini).

________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
Commenter la réponse de cptpingu
Messages postés
21
Date d'inscription
mardi 11 août 2009
Dernière intervention
20 juin 2011
0
Merci
Yep.

J'étais persuadé que mon erreur venait d'un "static", je suis passé à côté de la destruction de l'objet en fin d'appel de méthode.

Merci beaucoup à vous deux !

Bonne journée
Commenter la réponse de Inutqen

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.