Question : Variables static/méthode static/initialisation

Résolu
Inutqen Messages postés 21 Date d'inscription mardi 11 août 2009 Statut Membre Dernière intervention 20 juin 2011 - 11 mai 2011 à 11:17
Inutqen Messages postés 21 Date d'inscription mardi 11 août 2009 Statut Membre 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 !

10 réponses

cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
11 mai 2011 à 15:16
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
3
pop70 Messages postés 181 Date d'inscription mardi 6 avril 2010 Statut Membre Dernière intervention 7 janvier 2012 10
11 mai 2011 à 15:40
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
3
pop70 Messages postés 181 Date d'inscription mardi 6 avril 2010 Statut Membre Dernière intervention 7 janvier 2012 10
11 mai 2011 à 12:43
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
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
11 mai 2011 à 13:16
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
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Inutqen Messages postés 21 Date d'inscription mardi 11 août 2009 Statut Membre Dernière intervention 20 juin 2011
11 mai 2011 à 14:16
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...
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
11 mai 2011 à 14:54
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
0
Inutqen Messages postés 21 Date d'inscription mardi 11 août 2009 Statut Membre Dernière intervention 20 juin 2011
11 mai 2011 à 14:58
Merci, je regarde ça tout de suite.

Une idée de la raison du pourquoi du comment mon code plante ?
0
Inutqen Messages postés 21 Date d'inscription mardi 11 août 2009 Statut Membre Dernière intervention 20 juin 2011
11 mai 2011 à 15:26
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 ?
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
11 mai 2011 à 15:34

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
0
Inutqen Messages postés 21 Date d'inscription mardi 11 août 2009 Statut Membre Dernière intervention 20 juin 2011
11 mai 2011 à 15:45
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
0
Rejoignez-nous