Partage
  • Partager sur Facebook
  • Partager sur Twitter

Javaquarium C++

Sujet résolu
    29 mai 2020 à 12:26:15

    Bonjour à tous, j'essaie de faire le Javaquarium en C++ et je suis confronté à plusieurs erreur :

    Je déclare la classe Aquarium dans Aquarium.h et la classe Poisson dans poisson.h. Un prototype de Poisson a un paramètre de type Aquarium, et du coup ça plante : identificateur 'Aquarium' (j'ai inclus Aquarium.h).

    J'ai créé une méthode manger (type void et argument de type Aquarium) que j'utilise à plusieurs endroits et le compilo me dit qu'il est impossible de trouver la surcharge, qu'elle ne prend pas 1 argument et qu'elle n'a pas de pointeur this (alors qu'elle est dans une classe). 

    Le code :

    Aquarium.h

    #pragma once
    #include <iostream>
    #include <vector>
    #include "Poissons.h"
    using namespace std;
    class Aquarium {
    public:
    	Aquarium(int poissons = 0, int algues = 0);
    	int get_poissons() const;
    	int get_algues() const;
    	void ajouter_algue();
    	void enlever_algue();
    	void tour() const;
    	void enlever_poisson();
    	void ajouter_poisson(Poisson poisson);
    	vector<Poisson> get_liste_poissons() const;
    private:
    	int _poissons; int _algues;
    	vector<Poisson> _liste_poissons;
    };
    

    Poissons.h

    #pragma once
    #include "Aquarium.h"
    #include <iostream>
    using namespace std;
    class Poisson {
    public:
    	Poisson(string nom, string sexe, string espece);
    	Poisson();
    	string get_nom();
    	void mourir();
    	string get_sexe();
    	string get_espece();
    	void manger(Aquarium a);
    protected:
    	string _nom;
    	string _sexe;
    	string _espece;
    };
    class Carnivore : public Poisson {
    public:
    	Carnivore(string nom, string sexe, string espece);
    	Carnivore();
    };
    ostream& operator<<(ostream& flux, Carnivore a);
    class Herbivore : public Poisson {
    public:
    	Herbivore(string nom, string sexe, string espece);
    	Herbivore();
    };


    Aquarium.cpp

    #include <iostream>
    #include <ctime>
    #include <cstdlib>
    #include "Aquarium.h"
    #include "Poissons.h"
    using namespace std;
    Aquarium::Aquarium(int poissons, int algues) {
    	srand(time(NULL));
    	if (poissons < 0) poissons = 0;
    	if (algues < 0) algues = 0;
    	if (poissons > 1) {
    		string type;
    		for (int i = 0; i < poissons; i++) {
    			type = vector<string>({ "Carnivore", "Herbivore" })[rand() % 2];
    			if (type == "Carnivore") this->ajouter_poisson(Carnivore());
    			else  this->ajouter_poisson(Herbivore());
    		}
    	}
    	_poissons = poissons; _algues = algues;
    }
    vector<Poisson> Aquarium::get_liste_poissons() const{ 
    	return _liste_poissons;
    }
    void Aquarium::tour() const {
    	if (get_algues() > 1) { cout << "Il y a " << get_algues() << " algues"; }
    	else { cout << "Il y a " << get_algues() << " algue"; }
    	if (get_poissons() > 1) { cout << " et " << get_poissons() << " poissons." << endl; }
    	else { cout << " et " << get_poissons() << " poisson." << endl; }
    	cout << "Liste des poissons :" << endl;
    	char tab(9); // code ascii pour tabulation
    	vector<Poisson> liste = get_liste_poissons();
    	for (int poisson = 0; poisson < int(_liste_poissons.size()); poisson++) {
    		cout << tab << liste[poisson].get_nom() << " : " << liste[poisson].get_sexe()
    			<< " " << liste[poisson].get_espece() << endl;
    	}
    }
    int Aquarium::get_poissons() const {
    	return _poissons;
    }
    int Aquarium::get_algues() const {
    	return _algues;
    }
    void Aquarium::ajouter_algue() {
    	_algues++;
    }
    void Aquarium::enlever_algue() {
    	if (_algues != 0) _algues--;
    }
    void Aquarium::ajouter_poisson(Poisson poisson) {
    	_poissons++;
    	_liste_poissons.push_back(poisson);
    }
    void Aquarium::enlever_poisson() {
    	if (_poissons != 0) _poissons--;
    }

    Carnivores.cpp

    #include <iostream>
    #include <vector>
    #include "Aquarium.h"
    #include "Poissons.h"
    #include <ctime>
    #include <cstdlib>
    using namespace std;
    Carnivore::Carnivore(string nom, string sexe, string espece) {
    	_nom = nom; _sexe = sexe; _espece = espece;
    }
    Carnivore::Carnivore() {
    	string e_aigu = string(1, char(130));
    	string e_grave = string(1, char(138));
    	vector<string> noms = { "Albert", "Arlette", "Bertrand", ("B" + e_aigu + "atrice"), "Claude", "Claire", "Didier",
    	"Delphine", ("Eug" + e_grave + "ne"), "Emma", ("G" + e_aigu + "rard"), ("G" + e_aigu + "raldine"), "Jacques", "Jeanne",
    	("L" + e_aigu + "andre"), ("L" + e_aigu + "anne"), "Klaus", "Martin", "Robert", "Jacky" };
    	_nom = noms[rand() % noms.size()];
    	_sexe = vector<string>({ "male", "femelle" })[rand() % 2];
    }
    string _nom, _type, _sexe, _espece;
    ostream& operator<<(ostream& flux, Carnivore a) {
    	cout << a.get_nom();
    	return flux;
    }

    Herbivores.cpp

    #include <iostream>
    #include "Aquarium.h"
    #include "Poissons.h"
    #include <vector>
    #include <ctime>
    #include <cstdlib>
    using namespace std;
    Herbivore::Herbivore() {
    	srand(time(NULL));
    	string e_aigu = string(1, char(130));
    	string e_grave = string(1, char(138));
    	vector<string> noms = { "Albert", "Arlette", "Bertrand", ("B" + e_aigu + "atrice"), "Claude", "Claire", "Didier",
    	"Delphine", ("Eug" + e_grave + "ne"), "Emma", ("G" + e_aigu + "rard"), ("G" + e_aigu + "raldine"), "Jacques", "Jeanne",
    	("L" + e_aigu + "andre"), ("L" + e_aigu + "anne"), "Klaus", "Martin", "Robert", "Jacky" };
    	_nom = noms[rand() % noms.size()];
    	_sexe = vector<string>({ "male", "femelle" })[rand() % 2];
    }
    Herbivore::Herbivore(string nom, string sexe, string espece) {
    	_nom = nom; _sexe = sexe; _espece = espece;
    }

    Poisson.cpp

    #include <iostream>
    #include <vector>
    #include "Aquarium.h"
    #include "Poissons.h"
    #include <ctime>
    #include <cstdlib>
    using namespace std;
    Poisson::Poisson(string nom, string sexe, string espece) {
    	_nom = nom; _sexe = sexe; _espece = espece;
    }
    Poisson::Poisson() {
    	string e_aigu = string(1, char(130));
    	string e_grave = string(1, char(138));
    	vector<string> noms = { "Albert", "Arlette", "Bertrand", "B" + e_aigu + "atrice", "Claude", "Claire", "Didier",
    	"Delphine", "Eug" + e_grave + "ne", "Emma", ("G" + e_aigu + "rard"), "G" + e_aigu + "raldine", "Jacques", "Jeanne",
    	"L" + e_aigu + "andre", "L" + e_aigu + "anne", "Klaus", "Martin", "Robert", "Jacky" };
    	_nom = noms[rand() % noms.size()];
    	_sexe = vector<string>({ "male", "femelle" })[rand() % 2];
    }
    void Poisson::mourir() { delete this; }
    void Poisson::manger(Aquarium a) {
    	if (this->get_espece() == "sole" || this->get_espece() == "bar" || this->get_espece() == "carpe") a.enlever_algue();
    	else { vector<Poisson> liste = a.get_liste_poissons();  if (liste.size() != 0) liste[rand() % liste.size()].mourir(); }
    }
    string Poisson::get_sexe() { return _sexe; }
    string Poisson::get_espece() { return _espece; }
    string Poisson::get_nom() { return _nom; }
    bool operator==(Poisson a, Carnivore b) {
    	return (a.get_nom() == b.get_nom() && a.get_espece() == b.get_espece() 
    		&& a.get_sexe() == b.get_sexe());
    }
    bool operator==(Poisson a, Herbivore b) {
    	return (a.get_nom() == b.get_nom() && a.get_espece() == b.get_espece()
    		&& a.get_sexe() == b.get_sexe());
    }
    bool operator==(Poisson a, Poisson b) {
    	return (a.get_nom() == b.get_nom() && a.get_espece() == b.get_espece()
    		&& a.get_sexe() == b.get_sexe());
    }
    


    main.cpp

    #include <iostream>
    #include <string>
    #include "Aquarium.h"
    #include "Poissons.h"
    using namespace std;
    int main() {
    	Aquarium a;
    	int tours;
    	cout << "Combien de tours ?" << endl; cin >> tours;
    	for (int i = 0; i < tours; i++) {
    		cout << "Tour " << i << endl;
    		int action = 0;
    		a.tour();
    		do {
    			cout << "Que faire ? 1 pour ajouter algue et 2 pour ajouter poisson" << endl;
    			cin >> action;
    		} while (action != 1 && action != 2);
    		if (action == 1) a.ajouter_algue();
    		else {
    			string type = "poisson";
    			do {
    				cout << "herbivore ou carnivore ?" << endl; cin >> type;
    			} while (type != "herbivore" && type != "carnivore");
    			string nom, sexe = "non binaire", espece = "poisson";
    			cout << "Nom du poisson ?" << endl; cin.ignore(); getline(cin, nom);
    			do {
    				cout << "Sexe du poisson (male / femelle) ?" << endl; cin >> sexe;
    			} while (sexe != "male" && sexe != "femelle");
    			if (type == "herbivore") {
    				do {
    					cout << "Quelle espece de poisson (sole, bar, carpe) ?" << endl;
    					cin >> espece;
    				} while (espece != "sole" && espece != "bar" && espece != "carpe");
    				a.ajouter_poisson(Herbivore(nom, sexe, espece));
    			}
    			else {
    				do {
    					cout << "Quelle espece de poisson (merou, thon, clown) ?" << endl;
    					cin >> espece;
    				} while (espece != "merou" && espece != "thon" && espece != "clown");
    				a.ajouter_poisson(Carnivore(nom, sexe, espece));
    			}
    		}
    		// un poisson mange
    		cout << "Un poisson mange..." << endl;
    		if (a.get_poissons() > 0) a.get_liste_poissons()[rand() % a.get_liste_poissons().size()].manger(a);
    		a.tour();
    	}
    	return 0;
    }

    J'accepte toutes vos remarques (mêmes négatives) et vos conseils.

    Merci  à tous.

    -
    Edité par Chi_Iroh 29 mai 2020 à 12:27:59

    • Partager sur Facebook
    • Partager sur Twitter
      29 mai 2020 à 12:56:19

      ThomasSayen a écrit:

      Je déclare la classe Aquarium dans Aquarium.h et la classe Poisson dans poisson.h. Un prototype de Poisson a un paramètre de type Aquarium, et du coup ça plante : identificateur 'Aquarium' (j'ai inclus Aquarium.h).

      Si tu lisais le message en entier, il ne dit pas que "identificateur Aquarium", mais aussi ce qu'il en pense. Par exemple que ce n'est pas le nom d'un type connu. Et aussi, dans quel fichier source, à quelle ligne.

      C'est sans doute un problème d'ordre de déclarations. En C++ comme en C - et contrairement à Java -, on ne peut utiliser un nom de type que si il a été déclaré AVANT.

      Dans a priori, il faudrait avoir une déclaration d'Aquarium avant la définition de Poisson. Mais la définition d'Aquarium nécessite aussi de savoir que Poisson est une classe.

      Donc on est dans un problème de définition mutuelle, qui t'a certainement été expliqué quelque part.

      Autre problème : dans tes appels de fonctions membres, tu passes les poissons et aquariums par COPIE.






      • Partager sur Facebook
      • Partager sur Twitter
        29 mai 2020 à 13:04:19

        Je n'ai pas creusé pour regarder le code.

        -> ton problème: https://cpp.developpez.com/faq/cpp/?page=Les-classes-en-Cplusplus#Comment-creer-deux-classes-qui-font-reference-l-une-a-l-autre

        Par contre, très très, mais vraiment très mauvaise pratique: on ne doit jamais faire de using dans u fichier d'en-tête.

        • 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.
          29 mai 2020 à 14:04:34

          Merci pour vos réponses, j'ai enlevé using et déclaré les classes dans les .h.

          Maintenant, il me reste un problème : quand un poisson carnivore mange, le programme appelle la fonction mourir() pour le poisson visé .

          void mourir() { delete this; }

          Quand mourir() est appelé, j'ai une exception levée (violation d'accès en lecture, _Pnext a été 0xDDDDDDE1.) dans xmemory. J'ai vu plusieurs fois que la violation d'accès est due aux pointeurs. Je sais que this est un pointeur mais du coup comment supprimer le poisson et pas juste l'enlever de la liste de l'aquarium (c'est pas propre et ça utilise de la mémoire pour rien.) ?

          • Partager sur Facebook
          • Partager sur Twitter
            29 mai 2020 à 14:12:56

            Tu as un problème de conception, liés aux différences fondamentales entre langages.

            En Java tu manipules des variables qui contiennent des références à des objets.

            En C++, tes variables contiennent des objets. Et si tu fais a = b;  a contient une COPIE de b. Alors qu'en Java, a désignera le MÊME OBJET que b.

            Donc si tu veux qu'un objet représente un poisson, et qu'un poisson ne soit représenté que par un unique objet (ce qu'on appelle avoir une sémantique d'entité), il faut que ton programme manipule explicitement des références (au sens C++) et des pointeurs.

            Ah, j'entends la sonnette, ce sont les petits camarades qui viennent prêcher pour les smart pointeurs qui sont venus délivrer le monde de l'enfer des pointeurs à poil inventés par Satan.

            -
            Edité par michelbillaud 29 mai 2020 à 14:14:21

            • Partager sur Facebook
            • Partager sur Twitter
              29 mai 2020 à 14:15:36

              Salut ! Juste une petite remarque, la plupart du temps, en C++ on préfère utiliser des fonctionnalités de la bibliothèques standard (particulièrement celle de C++11 et des version ultérieures) que de devoir piocher dans les fonctionnalités du langage C. La paire rand()/srand() c'est du C, à la place on peut utiliser l'en-tête <random> qui fournit du stuff en rapport avec la génération de nombres pseudo-aléatoires et tout un tas de trucs dans l'esprit. Voici un lien de doc.

              EDIT: C'est pas nous qui prêchons l'utilisation des pointeurs intelligents !! C'est RAII ! :-°

              On ne touche pas à this, lui il aime pas qu'on l'emmerde, il est autonome et il n'a pas besoin d'aide.

              -
              Edité par Daimyo_ 29 mai 2020 à 14:20:01

              • Partager sur Facebook
              • Partager sur Twitter
                29 mai 2020 à 14:17:12

                D'accord donc pour chaque méthode de classe, je met des références aux paramètres. Et du coup, quand je remplacerai tous les paramètres par des références, delete this fonctionnera ou pas ?

                Parce que j'ai suivi le cours sur C++ mais que j'ai pas trop compris l'utilité des pointeurs.

                Et pour <random> j'ai déjà testé mais j'ai trouvé que la notation était un peu lourde pour le peu que je m'en servais. Quand j'aurai à faire de plus gros programmes avec plus d'aléatoires je m'en servirai.

                -
                Edité par Chi_Iroh 29 mai 2020 à 14:21:42

                • Partager sur Facebook
                • Partager sur Twitter
                  29 mai 2020 à 14:24:58

                  Si tu n'as pas compris le fonctionnement des pointeurs, replonge toi dans ton cours et documente toi. Faire du C++ sans comprendre les pointeurs c'est faire du Java sans comprendre l'intérêt de l'encapsulation (comparaison stupide hein...). Tu trouves des tonnes d'explication sur les pointeurs, elles sont parfois bonnes, parfois mauvaises, moi j'ai bien compris l'intérêt du truc grâce à (en partie):

                  Et encore une fois, tu n'as pas à t'occuper de this.

                  Ah et je viens de retrouver un article du blog de @Fvirtmann, qui passe par une métaphore poussée.

                  -
                  Edité par Daimyo_ 29 mai 2020 à 14:28:20

                  • Partager sur Facebook
                  • Partager sur Twitter
                    29 mai 2020 à 14:27:39

                    Pour rigoler, j'attend que la secte des Smart Pointers tente de fournir une explication accessible aux débutants :-) sans dire qu'un pointeur c'est une donnée qui contient l'adresse d'une (autre) donnée (*)

                    (*) ce qui est faux pour les pointeurs intelligents, puisqu'un shared pointer contient (dans les implémentations habituelles) l'adresse d'un "bloc de controle" avec compteur de références etc.



                    • Partager sur Facebook
                    • Partager sur Twitter
                      29 mai 2020 à 14:42:31

                      Tu veux dire quoi par le fait qu'un shared_ptr contienne l'adresse d'un bloc de contrôle ? Parce que dans la documentation il est cité qu'un shared_ptr contient un pointeur sur un objet et que la particularité de ce type de pointeur intelligent c'est que l'ownership peut être partagé (d'où le nom xD).

                      EDIT: Je viens de lire la doc de Microsoft, et en effet, ils parlent bien d'un bloc de contrôle. Ce qui parait logique quand on comprend le comportement d'un shared_ptr. Mais en plus de contenir l'adresse du bloc de contrôle, ils contiennent l'adresse de la ressource partagée.

                      Mais pourquoi ne pas utiliser un membre statique à la place d'un pointeur sur le bloc de contrôle ? Parce que c'est le bloc de contrôle qui gère la destruction de la ressource partagée ?

                      -
                      Edité par Daimyo_ 29 mai 2020 à 14:52:08

                      • Partager sur Facebook
                      • Partager sur Twitter
                        29 mai 2020 à 14:58:18

                        Note:

                        Une chose a faire avec ce type d'exercice, pour eviter les mauvaises surprises, et peut être comprendre pourquoi on a le droit de faire ceci, et pas le droit de faire cela, est de t'assurer que tes classes respectent la bonne sémantique.

                        En l'occurence, les aquariums, les poissons, les algue sont des entités, ce qui implique que les classes les représentant ne doivent être ni copiable, ni assignable.

                        PS: C'est d'ailleur la première question que tu doits te poser dès que tu décides de créer une classe:
                        Quelle est sa sémantique ? Valeur ou Entité ?

                        -
                        Edité par Deedolith 29 mai 2020 à 15:00:41

                        • Partager sur Facebook
                        • Partager sur Twitter
                          29 mai 2020 à 15:37:51

                          Daimyo_ a écrit:

                          Tu veux dire quoi par le fait qu'un shared_ptr contienne l'adresse d'un bloc de contrôle ? Parce que dans la documentation il est cité qu'un shared_ptr contient un pointeur sur un objet et que la particularité de ce type de pointeur intelligent c'est que l'ownership peut être partagé (d'où le nom xD).

                          EDIT: Je viens de lire la doc de Microsoft, et en effet, ils parlent bien d'un bloc de contrôle. Ce qui parait logique quand on comprend le comportement d'un shared_ptr. Mais en plus de contenir l'adresse du bloc de contrôle, ils contiennent l'adresse de la ressource partagée.

                          Mais pourquoi ne pas utiliser un membre statique à la place d'un pointeur sur le bloc de contrôle ? Parce que c'est le bloc de contrôle qui gère la destruction de la ressource partagée ?

                          -
                          Edité par Daimyo_ il y a 36 minutes


                          Un membre statique ? Pour que tous les pointeurs intelligents désignent la même instance ????

                          Dans https://fr.cppreference.com/w/cpp/memory/shared_ptr

                          <<

                          Notes d'implémentation

                          Dans une implémentation typique, std::shared_ptr conserve deux pointeurs :

                          • Un pointeur vers l'objet géré
                          • Un pointeur vers le bloc de contrôle

                          Le bloc de contrôle est un objet alloué dynamiquement qui conserve :

                          • soit un pointeur vers l'objet géré, soit l'objet géré lui-même
                          • le destructeur (type-erased)
                          • l'allocateur (type-erased)
                          • le nombre de shared_ptrs qui possèdent l'objet géré
                          • le nombre de weak_ptrs qui pointent sur l'objet géré

                          >>


                          ---

                          Lorsqu'on détruit un shared_pointer, son destructeur met à jour le compteur de références dans le bloc de contrôle, et décide de libérer la ressource si il n'y a plus de "co-propriétaires" de l'objet.

                          -
                          Edité par michelbillaud 29 mai 2020 à 15:41:31

                          • Partager sur Facebook
                          • Partager sur Twitter
                            29 mai 2020 à 16:07:27

                            D'accord c'est plus clair comme ça ! Merci. J'avais pensé à un membre statique qui ferait office de compteur pour les instances pointant sur la ressource partagée mais vu le traitement à faire pour que tout soit géré correctement ça serait minimaliste. Je ne pensais pas que le bloc de contrôle aurait autant de CoNtRoLe (ça arrive d'être stupide...) sur tout ce qui se trouve autour des shared_ptr.
                            • Partager sur Facebook
                            • Partager sur Twitter
                              29 mai 2020 à 17:39:16

                              La (mauvaise) idée d'utiliser un compteur statique vous a été AIMABLEMENT suggérée par le (très mauvais) exemple qui sert souvent d'introduction à la notion de membre statique, à savoir

                              "on veut créer des Machins, en leur attribuant automatiquement un numéro de série, qui s'incrémente à chaque fois"

                              La "solution" habituelle : ah ben y a qu'à mettre un compteur dans la classe.

                              #include <iostream>
                              
                              class Machin {
                               private:
                                  static int dernier_numero;
                                  int m_numero;
                                  std::string m_nom;
                              public:
                                  Machin(const std::string &nom)
                                   : m_numero(dernier_numero ++)
                                   , m_nom(nom)
                                  {}
                                  std::string to_string() const {
                                      return m_nom + "#" + std::to_string(m_numero);
                                  }
                              };
                              
                              int Machin::dernier_numero = 1;
                              
                              int main() {
                                  Machin m1("toto"), m2("titi");
                                  std::cout << m1.to_string() << std::endl
                                              << m2.to_string() << std::endl;
                                  return 0;
                              }


                              Critique : Le compteur unique pour tout le monde répand une délicate odeur de Singleton.

                              Bon, on va dire que fabriquer des Machins et les estampiller, c'est le boulot d'un Fabricant de Machins. Y a qu'à en faire un objet. On en aura autant qu'on veut, c'est à eux qu'on s'adresse pour avoir un nouveau machin.

                              #include <iostream>
                              
                              class Machin {
                               private:
                                  int m_numero;
                                  std::string m_nom;
                              public:
                                  Machin(const std::string &nom, int numero)
                                   : m_numero(numero)
                                   , m_nom(nom)
                                  {}
                                   std::string to_string() const {
                                      return m_nom + "#" + std::to_string(m_numero);
                                  }
                              };
                              
                              class Fabricant {
                              private:
                                  int m_num = 1;
                              public:
                                  Fabricant(int n = 0)  
                                      : m_num {n}
                                  {}
                                  Machin fabrique(const std::string & nom) {
                                      return {nom, m_num++};       // le voila, ton machin.
                                  }
                              };
                              
                              int main() {
                                  Fabricant f(1000);
                                  Machin m1 = f.fabrique("toto"), 
                                         m2 = f.fabrique("titi");
                                  std::cout << m1.to_string() << std::endl
                                            << m2.to_string() << std::endl;
                                  return 0;
                              }



                              -
                              Edité par michelbillaud 29 mai 2020 à 18:46:45

                              • Partager sur Facebook
                              • Partager sur Twitter
                                29 mai 2020 à 18:15:23

                                Merci pour vos réponses, je vais essayer de me débrouiller.

                                Bonne soirée à tous.

                                • Partager sur Facebook
                                • Partager sur Twitter

                                Javaquarium C++

                                × 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.
                                • Editeur
                                • Markdown