IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Fonction amie étant méthode d'une autre classe


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 28
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Par défaut Fonction amie étant méthode d'une autre classe
    Bonjour,

    Je suis en train d'écrire un petit programme pour entretenir le peu de niveau que j'ai en C++ et je suis bloqué.
    Avant d'expliquer, voici les fichiers que j'ai (.h et .cpp) :

    Interface.h :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    #ifndef DEF_INTERFACE
    #define DEF_INTERFACE
     
    #include "Piece.h"
     
    class Interface{
    public:
    	void grosseMethode();
     
    private:
    	int trouverLigneAtterissage() const;
    	Piece* p_;
    };
     
    #endif
    Interface.cpp :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include "Interface.h"
     
    void Interface::grosseMethode() {
    	// ...
    	// Fonction à ajouter
    }
     
    int Interface::trouverLigneAtterissage() const{
    	// Calculs
    	return ligneAtterissage;
    }
    Piece.h :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #ifndef DEF_PIECE
    #define DEF_PIECE
     
    class Piece{
    protected:
    	int ligneOrigine_;
    };
     
    #endif
    Ce que je cherche à faire, c'est créer une méthode setAltitudeFinale que j’appelerai dans grosseMethode(), qui utilise trouverLigneAtterissage() et qui modifie ligneOrigine_ MAIS je voudrais éviter de créer une méthode publique setLigneOrigine() dans la classe pièce car je veux que seule cette méthode setAltitudeFinale puisse toucher à l'attribut ligneOrigine_ à l'extérieur de la classe Piece.

    J'ai donc pensé à utiliser les friend :
    J'ai ajouté la méthode void setAltitudeFinale(Piece* p); dans la classe Interface et je l'ai déclaré comme amie dans la classe Piece :

    Interface.h :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #ifndef DEF_INTERFACE
    #define DEF_INTERFACE
     
    #include "Piece.h"
     
    class Interface{
    public:
    	void grosseMethode();
     
    private:
    	int trouverLigneAtterissage() const;
    	void setAltitudeFinale(Piece* p);
    	Piece* p_;
    };
     
    #endif
    Interface.cpp :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include "Interface.h"
     
    void Interface::grosseMethode() {
    	// ...
    	setAltitudeFinale(p_);
    }
     
    int Interface::trouverLigneAtterissage() const{
    	// Calculs
    	return ligneAtterissage;
    }
    Piece.h :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #ifndef DEF_PIECE
    #define DEF_PIECE
     
    class Piece{
    protected:
    	friend void Interface::setAltitudeFinale(Piece* p);
    	int ligneOrigine_;
    };
     
    #endif
    Alors déjà première question, mais je ne sais pas si je dois faire l'implémentation de la méthode setAltitudeFinale dans Interface.cpp ou dans Piece.cpp, j'aurais tendance à la faire dans Interface mais je suis pas sûr à 100% donc si vous pouviez me le confirmer ça serait génial ! Dans tous les cas, l'implémentation donne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void Interface::setAltitudeFinale(Piece* p) {
    	int ligneAtterissage = trouverLigneAtterissage();
    	p->ligneOrigine_ = ligneAtterissage;
    }
    Quand j'essaie de faire ça, si je mets l'implémentation dans Interface.cpp, Visual Studio me souligne ligneOrigine_ et me dit que ce membre est inaccessible.
    Si je déplace l'implémentation dans Piece.cpp, il ne reconnait pas la classe Interface (et il m'empêche d'utiliser ligneOrigine_ et trouverLigneAtterissage). Quand j'ajoute #include "Interface.h" dans Piece.h, Visual Studio ne me souligne plus rien mais quand je compile, j'ai plein d'erreurs qui viennent (selon moi) du fait que Piece.h inclus Interface.h et que Interface.h inclus Piece.h.

    J'avais pensé à faire l'inverse, à créer la méthode setAltitudeFinale() dans la classe Piece mais vu que cette méthode ne prendrait pas d'Interface en paramètre, je voyais pas comment appeler trouverLigneAtterissage().

    J'espère que j'ai été assez clair sur ce que j'ai envie de faire. Si vous pouvez m'aider ça serait vraiment super, j'espère au moins que ce que j'essaie de faire est possible.
    Si jamais vous voyez une meilleure façon de le faire (c'est à dire appeler une fonction/méthode dans grosseMethode() qui peut appeler trouverLigneAtterissage() et modifier ligneOrigine_), ou une autre façon de voir les choses au niveau conceptuel, je suis preneur !

    Merci d'avance !

  2. #2
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 487
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 487
    Par défaut
    Le problème, c'est que votre fonction "friendlisé" par la classe Piece "setAltitudeFinale" est private :
    https://stackoverflow.com/questions/...-another-class

  3. #3
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 28
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Par défaut
    D'accord je savais même pas que ça pouvait problème. Je pensais à tord que le fait de privatiser une méthode ne servait qu'à la rendre inaccessible en dehors de la classe.
    J'ai passé la méthode setAltitudeFinale(Piece* p) en public dans Interface mais j'ai toujours un problème inhérent au fait que dans Piece.h.

    J'ai regarder pour faire une déclaration anticipé dans la classe Interface dans la classe Piece mais ça ne fonctionne pas, il faut que je mette toutes les méthodes et au final elles entrent en conflit avec les "vraies" méthodes.
    Je me demande sérieusement, ce j'essaie de faire est vraiment réalisable ? Est ce que ma question est basique ? J'ai beau chercher je n'ai pas trouvé d'exemple similaire.

    J'aimerais vraiment aussi savoir où est ce que je suis censé écrire l'implémentation d'une méthode friend. Est ce que je dois l'écrire dans la classe d'origine ou dans la classe amie ?

    Merci !

  4. #4
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 487
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 487
    Par défaut
    D'accord je savais même pas que ça pouvait problème.
    Moi non plus, mais si l'on suit la logique de "friend", ce n'est pas un moyen de contourner l'encapsulation contrairement à ce qu'indiquent certaines sources.
    C'est un moyen d'étendre "l'API" utilisable par le code client de la classe, faisant de la fonction amie quasiment une fonction de la classe.
    C'est donc assez logique que cette fonction amie soit "public".

    N'utilisez jamais "friend" pour contourner l'encapsulation car vous cacheriez une erreur de conception avec un outil à double-tranchant (friend), mais du mauvais côté du tranchant.

    Je pensais à tord que le fait de privatiser une méthode ne servait qu'à la rendre inaccessible en dehors de la classe.
    Tout à fait, d'où le manque de logique de faire d'une fonction amie une fonction "privée" d'une autre classe.

    Il faut faire en sorte de rendre les classes les plus indépendantes les unes des autres pour rendre leur utilisation la plus simple possible.

    J'ai passé la méthode setAltitudeFinale(Piece* p) en public dans Interface mais j'ai toujours un problème inhérent au fait que dans Piece.h.
    Je comprends pas trop le problème.
    Voici un code qui compile quand il est entièrement dans un cpp :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    class Piece;
     
    class Interfaced {
    public:
    	void grosseMethode();
    	void setAltitudeFinale(Piece* p);
     
    private:
    	int trouverLigneAtterissage() const;
    	Piece* p_;
    };
     
    class Piece {
    protected:
    	int ligneOrigine_;
    	friend void Interfaced::setAltitudeFinale(Piece* p);
    };
     
     
     
    void Interfaced::grosseMethode() {
    	// ...
    	setAltitudeFinale(p_);
    }
     
    int Interfaced::trouverLigneAtterissage() const {
    	// Calculs
    	return 5;// ligneAtterissage;
    }
     
    void Interfaced::setAltitudeFinale(Piece* p) {
    	int ligneAtterissage = trouverLigneAtterissage();
    	p->ligneOrigine_ = ligneAtterissage;
    }
    Il y a donc un couplage important entre les classes, malheureusement, mais encore "soluble".

    Le code source SVP.

    Je me demande sérieusement, ce j'essaie de faire est vraiment réalisable ? Est ce que ma question est basique ? J'ai beau chercher je n'ai pas trouvé d'exemple similaire.
    Vous utilisez peut-être une mauvaise méthode pour résoudre un problème.
    Si vous nous présentiez le problème original, on pourrait plus efficacement vous aiguiller.

    J'aimerais vraiment aussi savoir où est ce que je suis censé écrire l'implémentation d'une méthode friend. Est ce que je dois l'écrire dans la classe d'origine ou dans la classe amie ?
    Le compilateur sans fout, mais il est plus logique de mettre l'implémentation d'une fonction d'instance/de classe dans le .cpp de cette classe (la classe "Interfaced" dans votre exemple).
    Mais c'est quand même assez rare qu'une fonction d'instance/de classe soit un bon choix pour une fonction amie, on préfère très souvent des fonctions libres.

  5. #5
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 28
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Par défaut
    Citation Envoyé par bacelar Voir le message
    Vous utilisez peut-être une mauvaise méthode pour résoudre un problème.
    Si vous nous présentiez le problème original, on pourrait plus efficacement vous aiguiller.
    Alors en fait, j’essaie de coder un Tétris. Ma classe Interface correspond à tous les éléments qu'on voit, c'est à dire les pièces suivantes, les pièces mises de côté et la zone de jeu dans laquelle on fait tomber les pièces. Elle contient les méthodes qui vont agir sur ces pièces, vérifier les lignes pleines, vérifier que la partie n'est pas perdue, etc, et elle a pour attributs, entre autres, les différentes pièces (par agrégation et pas par composition), les coordonnées de la pièce active et des attributs relatifs à l'affichage.

    J'ai aussi une classe Piece qui est un classe abstraite et j'ai une classe pour chaque type de pièces qui hérite de la classe Piece. Dans cette classe Piece, je vérifie que je peux tourner et déplacer la pièce (en gros, les méthodes pour tourner et déplacer de la classe Interface appellent sur les pièces les méthodes pour tourner et déplacer de la classe Piece, c'est dans la classe pièce que je vérifie qu'on a le droit de se déplacer/tourner) et j'ai des attributs relatifs à la position de la pièce, à sa hauteur/largeur, ...

    Dans la classe Interface, j'ai une méthode qui me permet de "valider" une pièce :
    • elle calcule la ligne à laquelle je dois descendre la pièce : avec la méthode Interface::trouverLigneAtterissage()
    • elle la fait descendre à la bonne hauteur

    Puis d'autres méthodes prennent la relève, regarde si une ligne est complète, supprime les pièces quand il faut, compte les scores, ...

    Ce qui me pose problème, c'est que pour descendre ma pièce, il faut que j'accède aux attributs privés de la classe Piece car c'est ceux-là qui comptent vraiment. L'attribut relatif à la position de la pièce active dans la classe Interface ne sert que tant qu'elle est active, à partir du moment où elle est validée, je me sers des attributs de la classe Piece (par exemple, quand une ligne est pleine, je dois descendre toutes les pièces au-dessus de cette ligne).

    J'aimerais éviter de créer un setter pour la hauteur de la pièce, parce que pour moi aucune classe ne devrait toucher à la position de la pièce SAUF quand je viens de la valider.
    C'est pourquoi je voulais créer une méthode Interface::setAltitudeFinale(Piece* p) qui utiliserait la méthode Interface::trouverLigneAtterissage() puis qui modifierait directement les attributs de l'objet de la classe Piece. La seule solution que j'ai trouvé, c'est de définir cette méthode comme friend de la classe Piece, pour qu'elle ait accès au attributs privés de la classe Piece.

    Le fait est que quand j'essaie de faire comme ça, quand j'écris l'implémentation de Interface::setAltitudeFinale(Piece* p) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void Interface::setAltitudeFinale(Piece* p) {
    	int ligneAtterissage = trouverLigneAtterissage();
    	p->ligneOrigine_ = ligneAtterissage;
    }
    ligneOrigine_ est soulignée en rouge et on me dit que le membre Piece::ligneOrigine_ est inaccessible, alors que j'étais persuadé que c'était justement tout l'intérêt des fonctions amies que d'avoir accès aux attributs privés de la classe.
    Et j'ai le problème dû au fait que, vu que je n'inclus pas Interface.h dans Piece.h (vu que j'inclus déjà Piece.h dans Interface.h), la classe Piece ne reconnait pas dans friend void Interface::setAltitudeFinale(Piece* p); le fait que Interface est une classe.

    Si y a besoin, je veux bien mettre tout mon code, mais c'est vrai qu'il est un peu long, donc si ça sert à rien, j'évite.

    Merci beaucoup de prendre le temps de m'aider, j'ai déjà appris beaucoup grâce à ce post (l'intérêt de la privatisation) et rien que pour ça, ça valait le coup.
    Merci encore !

  6. #6
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 487
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 487
    Par défaut
    Ma classe Interface correspond à tous les éléments qu'on voit,
    Elle devrait alors se nommer "Visible", non ?

    Elle contient les méthodes qui vont agir sur ces pièces
    Alors soit elle porte mal son nom soit elle fait trop de chose.
    Afficher des choses et "agir" sur des choses ne devrait pas être dans la même classe.
    C'est des types de "services" qui n'ont rien à voir l'un avec l'autre.

    vérifier les lignes pleines
    Idem que pour le point précédent.
    Votre classe doit fournir un ensemble de services COHÉRENT, pas amalgamer tout ce qui traine.

    vérifier que la partie n'est pas perdue, etc,
    Bon, je pense que vous avez compris le message.

    et elle a pour attributs, entre autres, les différentes pièces (par agrégation et pas par composition), les coordonnées de la pièce active et des attributs relatifs à l'affichage.
    Bon, on va dire que c'est l'objet en charge de l'affichage de l'Interface Homme Machine du jeu (et uniquement de cela) => Gui est plus précis que "Interface", non ?

    Dans cette classe Piece, je vérifie que je peux tourner et déplacer la pièce (en gros, les méthodes pour tourner et
    Ouais, bof.
    C'est plus de la responsabilité d'une autre classe que de la classe pièce de savoir si elle peut tournée, non ?
    Elle ne connait ni les autres pièces sur le "terrain" ni les limite du terrain, etc...
    Je pencherais pour une classe "Game" pour implémenter ces fonctionnalités plutôt que dans "Piece".
    Quitte à implémenter dans "Piece" des fonctionnalités pour simplifier le travail de "Game".

    déplacer de la classe Interface appellent sur les pièces les méthodes pour tourner et déplacer de la classe Piece, c'est dans la classe pièce que je vérifie qu'on a le droit de se déplacer/tourner) et j'ai des attributs relatifs à la position de la pièce, à sa hauteur/largeur, ...

    Dans la classe Interface, j'ai une méthode qui me permet de "valider" une pièce :

    elle calcule la ligne à laquelle je dois descendre la pièce : avec la méthode Interface::trouverLigneAtterissage()
    elle la fait descendre à la bonne hauteur
    Je ne comprends pas toutes ces circonvolution.
    Moi, j'ai une classe Gui, qui gère l'IHM avec l'utilisateur, a une référence sur un objet Game lui même contenant la position "logique" de chaque pièce.
    Quand l'utilisateur appuie sur Espace, la Gui appel la méthode "Drop" du Game, Game calcule les nouvelles positions, notifie Gui que l'état du jeu a changé, Gui appelle une méthode de Game pour avoir l'état logique du jeu et converti cet état logique en une représentation graphique.

    Puis d'autres méthodes prennent la relève, regarde si une ligne est complète, supprime les pièces quand il faut, compte les scores, ...
    Que des trucs que la classe "Game" est à même de faire.

    Ce qui me pose problème, c'est que pour descendre ma pièce, il faut que j'accède aux attributs privés de la classe Piece
    Pourquoi ???
    Après la suppression de ligne, il y a des choses dans la grille de jeu qui ne sont associées à aucune "Piece". C'est plutôt l'objet Game qui doit gérer ces détails de descente.
    Game peut demander des trucs aux pièces, comme les coordonnées relative de leur base, mais rien de bien complexe.
    Je ne suis même pas sûr de l'utilité des classes "Piece", peut-être que la classe "Game" peut tous gérer elle-même.

    car c'est ceux-là qui comptent vraiment.
    Bin non, Game a bien plus de billes.

    L'attribut relatif à la position de la pièce active dans la classe Interface ne sert que tant qu'elle est active, à partir du moment où elle est validée, je me sers des attributs de la classe Piece
    Heu, vos vous retrouvez avec du code qui considère une variable "mutante" (qui change de type à la volée) ???
    Je pense pas que cela soit une manière simple de faire les choses.
    Je vous conseille de revoir votre code pour ne pas être contraint à de telles extrémités.

    (par exemple, quand une ligne est pleine, je dois descendre toutes les pièces au-dessus de cette ligne).
    Qui gère ça, si ce n'est une instance de "Game" ?

    J'aimerais éviter de créer un setter pour la hauteur de la pièce
    Les setters, c'est caca.

    parce que pour moi aucune classe ne devrait toucher à la position de la pièce SAUF quand je viens de la valider.
    Un "SAUF" temporelle, c'est chaud à faire en POO.
    Non, vous ne devriez jamais à faire en sorte qu'une API "s'invalide" dans le temps.
    Redesignez vos classes/conception pour ne pas avoir ce genre de contrainte.

    C'est pourquoi je voulais créer une méthode Interface::setAltitudeFinale(Piece* p) qui utiliserait la méthode Interface::trouverLigneAtterissage() puis qui modifierait directement les attributs de l'objet de la classe Piece.
    Que c'est compliqué.
    Pourquoi pas un simple :
    Gui->Game.Drop->Calcule par l'objet Game du résultat final au niveau de la grille ( en utilisant les services des objets "Piece" si besoin, mais il faut que les services des objets Piece soient en lien avec les pièces et pas avec les grilles ou les règles de rotations du jeu, etc...)

    La seule solution que j'ai trouvé, c'est de définir cette méthode comme friend de la classe Piece, pour qu'elle ait accès au attributs privés de la classe Piece.
    Quand c'est un choix par défaut, c'est clairement un mauvais choix.
    Comme déjà indiqué, simplifiez votre conception.

    En résumé, pourquoi n'avez vous pas une classe "Game" pour gérer les règles "business" du jeu ?

Discussions similaires

  1. Réponses: 7
    Dernier message: 03/02/2012, 17h15
  2. Appel d'une méthode d'une autre classe à partir d'un actionListener
    Par bisouJava dans le forum Débuter avec Java
    Réponses: 4
    Dernier message: 31/10/2011, 09h05
  3. Exécuter une méthode d'une autre classe
    Par guillaume17 dans le forum Débuter avec Java
    Réponses: 15
    Dernier message: 25/06/2008, 12h15
  4. Réponses: 2
    Dernier message: 01/06/2007, 08h57
  5. Problème pour appeler une méthode d'une autre classe
    Par tse_tilky_moje_imja dans le forum Général Python
    Réponses: 7
    Dernier message: 03/03/2006, 13h33

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo